diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 000000000..bb52cf12c
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,25 @@
+# top-most EditorConfig file
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+indent_style = space
+indent_size = 2
+max_line_length=120
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.go]
+indent_style = tab
+indent_size = tab
+
+# line bellow should be like "[*.{kt,kts}]" if your editor adds spaces, it will break...
+# https://github.com/pinterest/ktlint#intellij-idea-editorconfig-autoformat-issue
+# [*.{kt,kts}]
+# possible values: number (e.g. 2), "unset" (makes ktlint ignore indentation completely)
+# indent_size=2
+# insert_final_newline=true # true (recommended) / false
+# possible values: number (e.g. 120) (package name, imports & comments are ignored), "off"
+# it's automatically set to 100 on `ktlint --android ...` (per Android Kotlin Style Guide)
+# max_line_length=120
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 000000000..cc6903d5c
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,28 @@
+#
+# https://help.github.com/articles/dealing-with-line-endings/
+#
+# Set the default behavior, in case people don't have core.autocrlf set.
+* text eol=lf
+
+/gradlew -text
+*.md -text
+*.jar -text
+*.bat -text
+*.pcap binary
+*.blocks binary
+*.dll binary
+*.dylib binary
+*.so binary
+*.gz binary
+*.ssz binary
+*.ssz_snappy binary
+
+prover/docker/setup/full/proving_key.bin binary
+prover/docker/setup/full/verifying_key.bin binary
+prover/docker/setup/full/circuit.bin binary
+prover/docker/setup/full/sample-proof.bin binary
+
+prover/docker/setup/light/proving_key.bin binary
+prover/docker/setup/light/verifying_key.bin binary
+prover/docker/setup/light/circuit.bin binary
+prover/docker/setup/light/sample-proof.bin binary
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
new file mode 100644
index 000000000..aaa1345d2
--- /dev/null
+++ b/.github/workflows/codeql.yml
@@ -0,0 +1,71 @@
+# For most projects, this workflow file will not need changing; you simply need
+# to commit it to your repository.
+#
+# You may wish to alter this file to override the set of languages analyzed,
+# or to provide custom queries or build logic.
+#
+# ******** NOTE ********
+# We have attempted to detect the languages in your repository. Please check
+# the `language` matrix defined below to confirm you have the correct set of
+# supported CodeQL languages.
+#
+name: "CodeQL"
+
+on:
+ push:
+ branches: [ "main" ]
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ strategy:
+ fail-fast: false
+ matrix:
+ language: [ 'go', 'java', 'javascript', 'python' ]
+ # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
+ # Use only 'java' to analyze code written in Java, Kotlin or both
+ # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
+ # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v2
+ with:
+ languages: ${{ matrix.language }}
+ # If you wish to specify custom queries, you can do so here or in a config file.
+ # By default, queries listed here will override any specified in a config file.
+ # Prefix the list here with "+" to use these queries and those in the config file.
+
+ # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
+ # queries: security-extended,security-and-quality
+
+
+ # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
+ # If this step fails, then you should remove it and run the build manually (see below)
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v2
+
+ # âšī¸ Command-line programs to run using the OS shell.
+ # đ See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
+
+ # If the Autobuild fails above, remove it and uncomment the following three lines.
+ # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
+
+ # - run: |
+ # echo "Run, Build Application using script"
+ # ./location_of_script_within_repo/buildscript.sh
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v2
+ with:
+ category: "/language:${{matrix.language}}"
diff --git a/.github/workflows/coordinator.yml b/.github/workflows/coordinator.yml
new file mode 100644
index 000000000..17cdc1061
--- /dev/null
+++ b/.github/workflows/coordinator.yml
@@ -0,0 +1,182 @@
+name: Coordinator CI
+on:
+ push:
+ workflow_dispatch:
+ inputs:
+ coverage:
+ description: To generate test report
+ required: false
+ type: boolean
+ default: false
+ e2e-tests-with-ssh:
+ description: Run end to end tests with ability to ssh into environment
+ required: false
+ type: boolean
+ default: false
+ e2e-tests-logs-dump:
+ description: Dump logs after running end to end tests
+ required: false
+ type: boolean
+ default: false
+
+jobs:
+ changes:
+ runs-on: ubuntu-latest
+ name: Filter commit changes
+ outputs:
+ coordinator: ${{ steps.filter.outputs.coordinator }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ - name: Filter commit changes
+ uses: dorny/paths-filter@v2
+ id: filter
+ with:
+ base: ${{ github.ref }}
+ list-files: "json"
+ filters: |
+ coordinator:
+ - 'coordinator/**'
+ - 'testdata/**'
+ - 'buildSrc/**'
+ - 'jvm-libs/**'
+ - 'gradle/**'
+ - 'build.gradle'
+ - 'gradle.properties'
+ - 'settings.gradle'
+ - '.github/workflows/coordinator.yml'
+ - '.github/workflows/reuse-*.yml'
+ - 'config/common/traces-limits-v1.toml'
+ - 'config/coordinator/**'
+ - 'e2e/**'
+
+ store_image_name_and_tags:
+ uses: ./.github/workflows/reuse-store-image-name-and-tags.yml
+ with:
+ image_name: consensys/linea-coordinator
+
+ check_image_tags_exist:
+ needs: [ changes, store_image_name_and_tags ]
+ if: ${{ needs.changes.outputs.coordinator == 'false' }}
+ uses: ./.github/workflows/reuse-check-image-tags-exist.yml
+ with:
+ last_commit_tag: ${{ needs.store_image_name_and_tags.outputs.last_commit_tag }}
+ common_ancestor_tag: ${{ needs.store_image_name_and_tags.outputs.common_ancestor_tag }}
+ image_name: ${{ needs.store_image_name_and_tags.outputs.image_name }}
+ secrets: inherit
+
+ coordinator-tag-only:
+ needs: [ changes, store_image_name_and_tags, check_image_tags_exist ]
+ if: ${{ needs.changes.outputs.coordinator == 'false' }}
+ uses: ./.github/workflows/reuse-image-tag-push.yml
+ with:
+ commit_tag: ${{ needs.store_image_name_and_tags.outputs.commit_tag }}
+ last_commit_tag: ${{ needs.store_image_name_and_tags.outputs.last_commit_tag }}
+ common_ancestor_tag: ${{ needs.store_image_name_and_tags.outputs.common_ancestor_tag }}
+ develop_tag: ${{ needs.store_image_name_and_tags.outputs.develop_tag }}
+ untested_tag_suffix: ${{ needs.store_image_name_and_tags.outputs.untested_tag_suffix }}
+ image_name: ${{ needs.store_image_name_and_tags.outputs.image_name }}
+ last_commit_tag_exists: ${{ needs.check_image_tags_exist.outputs.last_commit_tag_exists }}
+ common_ancestor_commit_tag_exists: ${{ needs.check_image_tags_exist.outputs.common_ancestor_commit_tag_exists }}
+ secrets: inherit
+
+ run-tests:
+ needs: [ changes, store_image_name_and_tags, coordinator-tag-only ]
+ if: ${{ always() && (needs.changes.outputs.coordinator == 'true' || needs.coordinator-tag-only.result != 'success' || needs.coordinator-tag-only.outputs.image_tagged != 'true') }}
+ runs-on: ubuntu-22.04
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ ref: ${{ github.head_ref }}
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 18.15.0
+ cache-dependency-path: contracts/package-lock.json
+ - name: Npm install
+ working-directory: contracts
+ run: npm ci --no-audit
+ - name: Install gradle
+ uses: gradle/gradle-build-action@v2
+ - name: Run tests
+ uses: nick-fields/retry@v2
+ with:
+ max_attempts: 3
+ retry_on: error
+ timeout_minutes: 20
+ command: |
+ ./gradlew coordinator:app:buildNeeded ${{ inputs.coverage && 'check jacocoRootReport' || '' }} --no-daemon
+
+ build-and-publish:
+ needs: [ changes, store_image_name_and_tags, coordinator-tag-only ]
+ if: ${{ always() && (needs.changes.outputs.coordinator == 'true' || needs.coordinator-tag-only.result != 'success' || needs.coordinator-tag-only.outputs.image_tagged != 'true') }}
+ runs-on: ubuntu-22.04
+ env:
+ COMMIT_TAG: ${{ needs.store_image_name_and_tags.outputs.commit_tag }}
+ DEVELOP_TAG: ${{ needs.store_image_name_and_tags.outputs.develop_tag }}
+ UNTESTED_TAG_SUFFIX: ${{ needs.store_image_name_and_tags.outputs.untested_tag_suffix }}
+ IMAGE_NAME: ${{ needs.store_image_name_and_tags.outputs.image_name }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ ref: ${{ github.head_ref }}
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 18.15.0
+ cache-dependency-path: contracts/package-lock.json
+ - name: Install gradle
+ uses: gradle/gradle-build-action@v2
+ - name: Build dist
+ run: |
+ ./gradlew coordinator:app:shadowJar --no-daemon
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v2
+ - name: Set up Docker Buildx
+ id: buildx
+ uses: docker/setup-buildx-action@v2
+ - name: Login to Docker Hub
+ if: github.event_name != 'pull_request'
+ uses: docker/login-action@v1
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+ - name: Docker meta
+ id: coordinator
+ uses: docker/metadata-action@v3
+ with:
+ images: ${{ env.IMAGE_NAME }}
+ - name: Build & push
+ uses: docker/build-push-action@v4
+ with:
+ context: .
+ build-contexts: jar=./coordinator/app/build/libs/
+ file: ./coordinator/Dockerfile
+ platforms: linux/amd64,linux/arm64
+ push: true
+ tags: |
+ ${{ env.IMAGE_NAME }}:${{ env.COMMIT_TAG }}-${{ env.UNTESTED_TAG_SUFFIX }}
+
+ run-e2e-tests:
+ needs: [ changes, store_image_name_and_tags, build-and-publish ]
+ if: ${{ always() && (needs.changes.outputs.coordinator == 'true' || needs.build-and-publish.result == 'success') }}
+ uses: ./.github/workflows/reuse-run-e2e-tests.yml
+ with:
+ commit_tag: ${{ needs.store_image_name_and_tags.outputs.commit_tag }}
+ untested_tag_suffix: ${{ needs.store_image_name_and_tags.outputs.untested_tag_suffix }}
+ e2e-tests-with-ssh: ${{ false && inputs.e2e-tests-with-ssh }}
+ e2e-tests-logs-dump: ${{ false && inputs.e2e-tests-logs-dump }}
+ secrets: inherit
+
+ tag-after-run-tests-success:
+ needs: [ store_image_name_and_tags, run-tests, run-e2e-tests ]
+ if: ${{ always() && needs.run-tests.result == 'success' && needs.run-e2e-tests.outputs.tests_outcome == 'success' }}
+ uses: ./.github/workflows/reuse-tag-without-untested-suffix.yml
+ with:
+ commit_tag: ${{ needs.store_image_name_and_tags.outputs.commit_tag }}
+ develop_tag: ${{ needs.store_image_name_and_tags.outputs.develop_tag }}
+ untested_tag_suffix: ${{ needs.store_image_name_and_tags.outputs.untested_tag_suffix }}
+ image_name: ${{ needs.store_image_name_and_tags.outputs.image_name }}
+ secrets: inherit
diff --git a/.github/workflows/prover.yml b/.github/workflows/prover.yml
new file mode 100644
index 000000000..0c81474f3
--- /dev/null
+++ b/.github/workflows/prover.yml
@@ -0,0 +1,260 @@
+name: Prover CI
+
+on:
+ push:
+ workflow_dispatch:
+ inputs:
+ e2e-tests-with-ssh:
+ description: Run end to end tests with ability to ssh into environment
+ required: false
+ type: boolean
+ default: false
+ e2e-tests-logs-dump:
+ description: Dump logs after running end to end tests
+ required: false
+ type: boolean
+ default: false
+
+env:
+ GOPROXY: "https://proxy.golang.org"
+
+jobs:
+ changes:
+ runs-on: ubuntu-latest
+ name: Filter commit changes
+ outputs:
+ prover: ${{ steps.filter.outputs.prover }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ - name: Filter commit changes
+ uses: dorny/paths-filter@v2
+ id: filter
+ with:
+ base: ${{ github.ref }}
+ list-files: "json"
+ filters: |
+ prover:
+ - 'prover/**'
+ - '.github/workflows/prover.yml'
+ - '.github/workflows/reuse-*.yml'
+ - 'constraints'
+
+ staticcheck:
+ needs:
+ - changes
+ if: ${{ needs.changes.outputs.prover == 'true' }}
+ runs-on: ubuntu-latest
+ steps:
+ - name: install Go
+ uses: actions/setup-go@v3
+ with:
+ go-version: 1.20.x
+ - name: checkout code
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ - uses: actions/cache@v3
+ with:
+ path: |
+ ~/go/pkg/mod
+ ~/.cache/go-build
+ ~/Library/Caches/go-build
+ %LocalAppData%\go-build
+ key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
+ restore-keys: |
+ ${{ runner.os }}-go-
+ - name: gofmt
+ working-directory: prover
+ run: if [[ -n $(gofmt -l .) ]]; then echo "please run gofmt"; exit 1; fi
+ - name: golangci-lint
+ uses: golangci/golangci-lint-action@v3
+ with:
+ working-directory: prover
+ args: --timeout=5m
+ - name: generated files should not be modified
+ working-directory: prover
+ run: |
+ go generate ./...
+ git update-index --assume-unchanged go.mod
+ git update-index --assume-unchanged go.sum
+ if [[ -n $(git status --porcelain) ]]; then echo "git repo is dirty after running go generate -- please don't modify generated files"; echo $(git diff);echo $(git status --porcelain); exit 1; fi
+
+ test:
+ if: ${{ needs.changes.outputs.prover == 'true' }}
+ strategy:
+ matrix:
+ go-version: [1.20.x]
+ os: [ubuntu-latest]
+ runs-on: ${{ matrix.os }}
+ needs:
+ - staticcheck
+ steps:
+ - name: install Go
+ uses: actions/setup-go@v3
+ with:
+ go-version: ${{ matrix.go-version }}
+ - name: checkout code
+ uses: actions/checkout@v3
+ - uses: actions/cache@v3
+ with:
+ path: |
+ ~/go/pkg/mod
+ ~/.cache/go-build
+ ~/Library/Caches/go-build
+ %LocalAppData%\go-build
+ key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
+ restore-keys: |
+ ${{ runner.os }}-go-
+ - name: Test
+ working-directory: prover
+ run: |
+ go test -p=1 -tags=nocorset,fuzzlight -timeout=30m ./...
+ - name: Test (32 bits & race)
+ working-directory: prover
+ if: (matrix.os == 'ubuntu-latest') && (matrix.go-version == '1.20.x')
+ run: |
+ go test -p=1 -tags=nocorset,fuzzlight -timeout=30m -short -race ./...
+
+ slack-workflow-status-failed:
+ if: failure()
+ name: post workflow status to slack
+ needs:
+ - staticcheck
+ - test
+ runs-on: ubuntu-latest
+ steps:
+ - name: Notify slack -- workflow failed
+ id: slack
+ uses: slackapi/slack-github-action@v1.23.0
+ with:
+ payload: |
+ {
+ "actor": "${{ github.actor }}",
+ "repo": "${{ github.repository }}",
+ "status": "FAIL",
+ "title": "${{ github.event.pull_request.title }}",
+ "pr": "${{ github.event.pull_request.head.ref }}"
+ }
+ env:
+ SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_CI_PROVER_FAIL }}
+
+ slack-workflow-status-success:
+ if: success()
+ name: post workflow status to slack
+ needs:
+ - staticcheck
+ - test
+ runs-on: ubuntu-latest
+ steps:
+ - name: Notify slack -- workflow succeeded
+ id: slack
+ uses: slackapi/slack-github-action@v1.23.0
+ with:
+ payload: |
+ {
+ "actor": "${{ github.actor }}",
+ "repo": "${{ github.repository }}",
+ "status": "SUCCESS",
+ "title": "${{ github.event.pull_request.title }}",
+ "pr": "${{ github.event.pull_request.head.ref }}"
+ }
+ env:
+ SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_CI_PROVER_SUCCESS }}
+
+ store_image_name_and_tags:
+ uses: ./.github/workflows/reuse-store-image-name-and-tags.yml
+ with:
+ image_name: consensys/linea-prover
+
+ check_image_tags_exist:
+ needs: [ changes, store_image_name_and_tags ]
+ if: ${{ needs.changes.outputs.prover == 'false' }}
+ uses: ./.github/workflows/reuse-check-image-tags-exist.yml
+ with:
+ last_commit_tag: ${{ needs.store_image_name_and_tags.outputs.last_commit_tag }}
+ common_ancestor_tag: ${{ needs.store_image_name_and_tags.outputs.common_ancestor_tag }}
+ image_name: ${{ needs.store_image_name_and_tags.outputs.image_name }}
+ secrets: inherit
+
+ prover-tag-only:
+ needs: [ changes, store_image_name_and_tags, check_image_tags_exist ]
+ if: ${{ needs.changes.outputs.prover == 'false' }}
+ uses: ./.github/workflows/reuse-image-tag-push.yml
+ with:
+ commit_tag: ${{ needs.store_image_name_and_tags.outputs.commit_tag }}
+ last_commit_tag: ${{ needs.store_image_name_and_tags.outputs.last_commit_tag }}
+ common_ancestor_tag: ${{ needs.store_image_name_and_tags.outputs.common_ancestor_tag }}
+ develop_tag: ${{ needs.store_image_name_and_tags.outputs.develop_tag }}
+ untested_tag_suffix: ${{ needs.store_image_name_and_tags.outputs.untested_tag_suffix }}
+ image_name: ${{ needs.store_image_name_and_tags.outputs.image_name }}
+ last_commit_tag_exists: ${{ needs.check_image_tags_exist.outputs.last_commit_tag_exists }}
+ common_ancestor_commit_tag_exists: ${{ needs.check_image_tags_exist.outputs.common_ancestor_commit_tag_exists }}
+ secrets: inherit
+
+ build-and-publish:
+ needs: [ changes, store_image_name_and_tags, prover-tag-only ]
+ if: ${{ always() && (needs.changes.outputs.prover == 'true' || needs.prover-tag-only.result != 'success' || needs.prover-tag-only.outputs.image_tagged != 'true') }}
+ runs-on: ubuntu-latest
+ env:
+ COMMIT_TAG: ${{ needs.store_image_name_and_tags.outputs.commit_tag }}
+ DEVELOP_TAG: ${{ needs.store_image_name_and_tags.outputs.develop_tag }}
+ UNTESTED_TAG_SUFFIX: ${{ needs.store_image_name_and_tags.outputs.untested_tag_suffix }}
+ IMAGE_NAME: ${{ needs.store_image_name_and_tags.outputs.image_name }}
+ name: Prover build and push
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ ssh-key: ${{ secrets.SELF_GITHUB_SSH_KEY }}
+ submodules: true
+ persist-credentials: false
+ - name: Login to Docker Hub
+ uses: docker/login-action@v2
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v2
+ - name: Show the "version" build argument
+ run: |
+ echo "We inject the commit tag in the docker image ${{ env.COMMIT_TAG }}"
+ echo COMMIT_TAG=${{ env.COMMIT_TAG }} >> $GITHUB_ENV
+ - name: Build and push prover image
+ uses: docker/build-push-action@v4
+ with:
+ context: .
+ file: ./prover/Dockerfile
+ build-args: |
+ VERSION=${{ env.COMMIT_TAG }}
+ RUSTFLAGS="-C target-cpu=x86-64-v3"
+ build-contexts: |
+ prover=prover/
+ corset=corset/
+ constraints=constraints/
+ platforms: linux/amd64
+ push: true
+ tags: |
+ ${{ env.IMAGE_NAME }}:${{ env.COMMIT_TAG }}-${{ env.UNTESTED_TAG_SUFFIX }}
+
+ run-e2e-tests:
+ needs: [ changes, store_image_name_and_tags, build-and-publish ]
+ if: ${{ always() && (needs.changes.outputs.coordinator == 'true' || needs.build-and-publish.result == 'success') }}
+ uses: ./.github/workflows/reuse-run-e2e-tests.yml
+ with:
+ commit_tag: ${{ needs.store_image_name_and_tags.outputs.commit_tag }}
+ untested_tag_suffix: ${{ needs.store_image_name_and_tags.outputs.untested_tag_suffix }}
+ e2e-tests-with-ssh: ${{ false && inputs.e2e-tests-with-ssh }}
+ e2e-tests-logs-dump: ${{ false && inputs.e2e-tests-logs-dump }}
+ secrets: inherit
+
+ tag-after-run-tests-success:
+ needs: [ store_image_name_and_tags, run-e2e-tests ]
+ if: ${{ always() && needs.run-e2e-tests.outputs.tests_outcome == 'success' }}
+ uses: ./.github/workflows/reuse-tag-without-untested-suffix.yml
+ with:
+ commit_tag: ${{ needs.store_image_name_and_tags.outputs.commit_tag }}
+ develop_tag: ${{ needs.store_image_name_and_tags.outputs.develop_tag }}
+ untested_tag_suffix: ${{ needs.store_image_name_and_tags.outputs.untested_tag_suffix }}
+ image_name: ${{ needs.store_image_name_and_tags.outputs.image_name }}
+ secrets: inherit
\ No newline at end of file
diff --git a/.github/workflows/reuse-check-image-tags-exist.yml b/.github/workflows/reuse-check-image-tags-exist.yml
new file mode 100644
index 000000000..1e8cd0919
--- /dev/null
+++ b/.github/workflows/reuse-check-image-tags-exist.yml
@@ -0,0 +1,56 @@
+name: Reusable check image tags exist
+on:
+ workflow_call:
+ inputs:
+ last_commit_tag:
+ required: true
+ type: string
+ common_ancestor_tag:
+ required: true
+ type: string
+ image_name:
+ required: true
+ type: string
+ outputs:
+ last_commit_tag_exists:
+ value: ${{ jobs.check_image_tags_exist.outputs.last_commit_tag_exists }}
+ common_ancestor_commit_tag_exists:
+ value: ${{ jobs.check_image_tags_exist.outputs.common_ancestor_commit_tag_exists }}
+ secrets:
+ DOCKERHUB_USERNAME:
+ required: true
+ DOCKERHUB_TOKEN:
+ required: true
+
+jobs:
+ check_image_tags_exist:
+ runs-on: ubuntu-latest
+ name: Check image tags exist
+ continue-on-error: true
+ env:
+ LAST_COMMIT_TAG: ${{ inputs.last_commit_tag }}
+ COMMON_ANCESTOR_TAG: ${{ inputs.common_ancestor_tag }}
+ IMAGE_NAME: ${{ inputs.image_name }}
+ outputs:
+ last_commit_tag_exists: ${{ steps.last_commit_image_exists.outputs.last_commit_tag_exists }}
+ common_ancestor_commit_tag_exists: ${{ steps.ancestor_commit_image_exists.outputs.common_ancestor_commit_tag_exists }}
+ steps:
+ - name: Login to Docker Hub
+ uses: docker/login-action@v2
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+ - name: Check last commit image tag exists
+ id: last_commit_image_exists
+ continue-on-error: true
+ run: |
+ echo last_commit_tag_exists=$(docker pull ${{ env.IMAGE_NAME }}:${{ env.LAST_COMMIT_TAG }} > /dev/null ; echo $?) >> $GITHUB_OUTPUT
+ - name: Check ancestor commit image tag exists
+ id: ancestor_commit_image_exists
+ continue-on-error: true
+ run: |
+ echo common_ancestor_commit_tag_exists=$(docker pull ${{ env.IMAGE_NAME }}:${{ env.COMMON_ANCESTOR_TAG }} > /dev/null ; echo $?) >> $GITHUB_OUTPUT
+ - name: Show outputs
+ run: |
+ echo "last_commit_tag_exists: ${{ steps.last_commit_image_exists.outputs.last_commit_tag_exists }}"
+ echo "common_ancestor_commit_tag_exists: ${{ steps.ancestor_commit_image_exists.outputs.common_ancestor_commit_tag_exists }}"
diff --git a/.github/workflows/reuse-image-tag-push.yml b/.github/workflows/reuse-image-tag-push.yml
new file mode 100644
index 000000000..1ec31d983
--- /dev/null
+++ b/.github/workflows/reuse-image-tag-push.yml
@@ -0,0 +1,78 @@
+name: Reusable image tag and push
+on:
+ workflow_call:
+ inputs:
+ commit_tag:
+ required: true
+ type: string
+ last_commit_tag:
+ required: true
+ type: string
+ common_ancestor_tag:
+ required: true
+ type: string
+ develop_tag:
+ required: true
+ type: string
+ untested_tag_suffix:
+ required: true
+ type: string
+ image_name:
+ required: true
+ type: string
+ last_commit_tag_exists:
+ required: true
+ type: string
+ common_ancestor_commit_tag_exists:
+ required: true
+ type: string
+ outputs:
+ image_tagged:
+ value: ${{ jobs.image_tag_push.outputs.image_tagged }}
+ secrets:
+ DOCKERHUB_USERNAME:
+ required: true
+ DOCKERHUB_TOKEN:
+ required: true
+
+jobs:
+ image_tag_push:
+ runs-on: ubuntu-latest
+ name: image tag and push
+ env:
+ LAST_COMMIT_TAG: ${{ inputs.last_commit_tag }}
+ COMMIT_TAG: ${{ inputs.commit_tag }}
+ COMMON_ANCESTOR_TAG: ${{ inputs.common_ancestor_tag }}
+ DEVELOP_TAG: ${{ inputs.develop_tag }}
+ UNTESTED_TAG_SUFFIX: ${{ inputs.untested_tag_suffix }}
+ LAST_COMMIT_TAG_EXISTS: ${{ inputs.last_commit_tag_exists }}
+ COMMON_ANCESTOR_COMMIT_TAG_EXISTS: ${{ inputs.common_ancestor_commit_tag_exists }}
+ IMAGE_NAME: ${{ inputs.image_name }}
+ outputs:
+ image_tagged: ${{ env.IMAGE_TAGGED }}
+ steps:
+ - name: Login to Docker Hub
+ uses: docker/login-action@v2
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+ - name: Initialize IMAGE_TAGGED value
+ run: |
+ echo IMAGE_TAGGED=false >> $GITHUB_ENV
+ - name: Tag Docker image with last commit tag with the commit hash plus w/o "untested" suffix
+ if: ${{ env.LAST_COMMIT_TAG != '0000000' && env.LAST_COMMIT_TAG_EXISTS == '0' }}
+ run: |
+ docker buildx imagetools create --tag ${{ env.IMAGE_NAME }}:${{ env.COMMIT_TAG }} ${{ env.IMAGE_NAME }}:${{ env.LAST_COMMIT_TAG }}
+ docker buildx imagetools create --tag ${{ env.IMAGE_NAME }}:${{ env.COMMIT_TAG }}-${{ env.UNTESTED_TAG_SUFFIX }} ${{ env.IMAGE_NAME }}:${{ env.LAST_COMMIT_TAG }}
+ echo IMAGE_TAGGED=true >> $GITHUB_ENV
+ - name: Tag Docker image with common ancestor commit tag with the commit hash plus w/o "untested" suffix
+ if: ${{ env.LAST_COMMIT_TAG == '0000000' && env.COMMON_ANCESTOR_COMMIT_TAG_EXISTS == '0' }}
+ run: |
+ docker buildx imagetools create --tag ${{ env.IMAGE_NAME }}:${{ env.COMMIT_TAG }} ${{ env.IMAGE_NAME }}:${{ env.COMMON_ANCESTOR_TAG }}
+ docker buildx imagetools create --tag ${{ env.IMAGE_NAME }}:${{ env.COMMIT_TAG }}-${{ env.UNTESTED_TAG_SUFFIX }} ${{ env.IMAGE_NAME }}:${{ env.COMMON_ANCESTOR_TAG }}
+ echo IMAGE_TAGGED=true >> $GITHUB_ENV
+ - name: Tag Docker image with develop if on main branch
+ if: ${{ github.ref == 'refs/heads/main' && env.LAST_COMMIT_TAG_EXISTS == '0' }}
+ run: |
+ docker buildx imagetools create --tag ${{ env.IMAGE_NAME }}:${{ env.DEVELOP_TAG }} ${{ env.IMAGE_NAME }}:${{ env.LAST_COMMIT_TAG }}
+ echo IMAGE_TAGGED=true >> $GITHUB_ENV
\ No newline at end of file
diff --git a/.github/workflows/reuse-run-e2e-tests.yml b/.github/workflows/reuse-run-e2e-tests.yml
new file mode 100644
index 000000000..f4bb60eed
--- /dev/null
+++ b/.github/workflows/reuse-run-e2e-tests.yml
@@ -0,0 +1,113 @@
+name: Reusable run e2e tests
+on:
+ workflow_call:
+ inputs:
+ commit_tag:
+ required: true
+ type: string
+ untested_tag_suffix:
+ required: true
+ type: string
+ e2e-tests-with-ssh:
+ description: Run end to end tests with ability to ssh into environment
+ required: false
+ type: boolean
+ default: false
+ e2e-tests-logs-dump:
+ description: Dump logs after running end to end tests
+ required: false
+ type: boolean
+ default: false
+ outputs:
+ tests_outcome:
+ value: ${{ jobs.run-e2e-tests.outputs.tests_outcome }}
+ secrets:
+ DOCKERHUB_USERNAME:
+ required: true
+ DOCKERHUB_TOKEN:
+ required: true
+
+jobs:
+ run-e2e-tests:
+ env:
+ COORDINATOR_TAG: ${{ inputs.commit_tag }}-${{ inputs.untested_tag_suffix }}
+ POSTMAN_TAG: ${{ inputs.commit_tag }}-${{ inputs.untested_tag_suffix }}
+ PROVER_TAG: ${{ inputs.commit_tag }}-${{ inputs.untested_tag_suffix }}
+ TRACES_API_TAG: ${{ inputs.commit_tag }}-${{ inputs.untested_tag_suffix }}
+ outputs:
+ tests_outcome: ${{ steps.run_e2e_tests.outcome }}
+ runs-on: ubuntu-22.04
+ steps:
+ - name: Setup upterm session
+ if: ${{ inputs.e2e-tests-with-ssh }}
+ uses: lhotari/action-upterm@v1
+ - name: Checkout
+ uses: actions/checkout@v3
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 18.15.0
+ cache-dependency-path: |
+ contracts/package-lock.json
+ e2e/package-lock.json
+ - name: Npm install - Contracts
+ working-directory: contracts
+ run: npm ci --no-audit
+ - name: Npm install - e2e
+ working-directory: e2e
+ run: npm ci
+ - name: Login to Docker Hub
+ if: github.event_name != 'pull_request'
+ uses: docker/login-action@v1
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+ - name: Setup Shomei directories
+ run: |
+ mkdir -p ../tmp/zkbesu-shomei/plugins/
+ mkdir -p ../tmp/local/shomei/
+ - name: Pull all images with retry
+ uses: nick-fields/retry@v2
+ with:
+ max_attempts: 10
+ retry_on: error
+ retry_wait_seconds: 30
+ timeout_minutes: 10
+ command: |
+ make pull-all-images-ci
+ - name: Spin up fresh environment with retry
+ uses: nick-fields/retry@v2
+ with:
+ max_attempts: 10
+ retry_on: error
+ retry_wait_seconds: 30
+ timeout_minutes: 10
+ command: |
+ make start-all-ci
+ on_retry_command: |
+ make clean-environment-ci
+ - name: List docker containers/images
+ continue-on-error: true
+ run: |
+ docker ps -la && docker images
+ docker container ls -a
+ docker logs coordinator
+ docker logs zkbesu-shomei
+ docker logs shomei
+ - name: Run e2e tests
+ id: run_e2e_tests
+ run: |
+ cd e2e
+ npm run test:e2e:local
+ - name: Show e2e tests result
+ if: always()
+ run: |
+ echo "E2E_TESTS_RESULT: ${{ steps.run_e2e_tests.outcome }}"
+ - name: Dump logs
+ if: ${{ always() && inputs.e2e-tests-logs-dump }}
+ run: |
+ docker logs coordinator
+ docker logs postman
+ docker logs prover
+ docker logs traces-api
+ docker logs shomei
+ docker logs zkbesu-shomei
diff --git a/.github/workflows/reuse-store-image-name-and-tags.yml b/.github/workflows/reuse-store-image-name-and-tags.yml
new file mode 100644
index 000000000..d326a0b3b
--- /dev/null
+++ b/.github/workflows/reuse-store-image-name-and-tags.yml
@@ -0,0 +1,58 @@
+name: Reusable store image name and tags
+on:
+ workflow_call:
+ inputs:
+ image_name:
+ required: true
+ type: string
+ outputs:
+ commit_tag:
+ value: ${{ jobs.store_image_name_and_tags.outputs.commit_tag }}
+ last_commit_tag:
+ value: ${{ jobs.store_image_name_and_tags.outputs.last_commit_tag }}
+ develop_tag:
+ value: ${{ jobs.store_image_name_and_tags.outputs.develop_tag }}
+ common_ancestor_tag:
+ value: ${{ jobs.store_image_name_and_tags.outputs.common_ancestor_tag }}
+ untested_tag_suffix:
+ value: ${{ jobs.store_image_name_and_tags.outputs.untested_tag_suffix }}
+ image_name:
+ value: ${{ inputs.image_name }}
+
+jobs:
+ store_image_name_and_tags:
+ runs-on: ubuntu-latest
+ name: Compute version tags
+ env:
+ REF_NAME: ${{ github.ref_name }}
+ EVENT_BEFORE: ${{ github.event.before }}
+ outputs:
+ commit_tag: ${{ steps.step2.outputs.COMMIT_TAG }}
+ last_commit_tag: ${{ steps.step2.outputs.LAST_COMMIT_TAG }}
+ common_ancestor_tag: ${{ steps.step2.outputs.COMMON_ANCESTOR_TAG }}
+ develop_tag: ${{ steps.step2.outputs.DEVELOP_TAG }}
+ untested_tag_suffix: ${{ steps.step2.outputs.UNTESTED_TAG_SUFFIX }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ - name: Get common ancestor commit
+ id: step1
+ run: |
+ git fetch --no-tags --depth=100 origin main ${{ env.REF_NAME }}
+ echo COMMON_ANCESTOR=$(git merge-base refs/remotes/origin/main refs/remotes/origin/${{ env.REF_NAME }}) >> $GITHUB_ENV
+ - name: Compute version tags
+ id: step2
+ run: |
+ echo COMMIT_TAG=$(git rev-parse --short "$GITHUB_SHA") >> $GITHUB_OUTPUT
+ echo LAST_COMMIT_TAG=$(git rev-parse --short "${{ env.EVENT_BEFORE }}") >> $GITHUB_OUTPUT
+ echo DEVELOP_TAG=develop >> $GITHUB_OUTPUT
+ echo COMMON_ANCESTOR_TAG=$(git rev-parse --short "${{ env.COMMON_ANCESTOR }}") >> $GITHUB_OUTPUT
+ echo UNTESTED_TAG_SUFFIX=untested >> $GITHUB_OUTPUT
+ - name: Show version tags
+ id: step3
+ run: |
+ echo "COMMIT_TAG: ${{ steps.step2.outputs.COMMIT_TAG }}"
+ echo "LAST_COMMIT_TAG: ${{ steps.step2.outputs.LAST_COMMIT_TAG }}"
+ echo "DEVELOP_TAG: ${{ steps.step2.outputs.DEVELOP_TAG }}"
+ echo "COMMON_ANCESTOR_TAG: ${{ steps.step2.outputs.COMMON_ANCESTOR_TAG }}"
+ echo "UNTESTED_TAG_SUFFIX: ${{ steps.step2.outputs.UNTESTED_TAG_SUFFIX }}"
diff --git a/.github/workflows/reuse-tag-without-untested-suffix.yml b/.github/workflows/reuse-tag-without-untested-suffix.yml
new file mode 100644
index 000000000..3132db402
--- /dev/null
+++ b/.github/workflows/reuse-tag-without-untested-suffix.yml
@@ -0,0 +1,40 @@
+name: Reusable tag without untested suffix
+on:
+ workflow_call:
+ inputs:
+ commit_tag:
+ required: true
+ type: string
+ develop_tag:
+ required: true
+ type: string
+ untested_tag_suffix:
+ required: true
+ type: string
+ image_name:
+ required: true
+ type: string
+ secrets:
+ DOCKERHUB_USERNAME:
+ required: true
+ DOCKERHUB_TOKEN:
+ required: true
+
+jobs:
+ tag-without-untested-suffix:
+ runs-on: ubuntu-latest
+ name: tag without untested suffix
+ steps:
+ - name: Login to Docker Hub
+ if: github.event_name != 'pull_request'
+ uses: docker/login-action@v1
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+ - name: Tag Docker image with develop if on main branch
+ if: ${{ github.ref == 'refs/heads/main' }}
+ run: |
+ docker buildx imagetools create --tag ${{ inputs.image_name }}:${{ inputs.develop_tag }} ${{ inputs.image_name }}:${{ inputs.commit_tag }}-${{ inputs.untested_tag_suffix }}
+ - name: Tag Docker image with the commit hash (without the "untested" suffix)
+ run: |
+ docker buildx imagetools create --tag ${{ inputs.image_name }}:${{ inputs.commit_tag }} ${{ inputs.image_name }}:${{ inputs.commit_tag }}-${{ inputs.untested_tag_suffix }}
diff --git a/.github/workflows/run-smc-tests.yml b/.github/workflows/run-smc-tests.yml
new file mode 100644
index 000000000..8b518ac2e
--- /dev/null
+++ b/.github/workflows/run-smc-tests.yml
@@ -0,0 +1,52 @@
+name: Smart contracts test
+
+on:
+ push:
+ paths:
+ - 'contracts/**'
+ - 'testdata/**'
+
+jobs:
+ run-contract-tests:
+ runs-on: ubuntu-latest
+ name: Run smart contracts tests
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ ref: ${{ github.head_ref }}
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 18.15.0
+ cache-dependency-path: contracts/package-lock.json
+ - name: Npm install
+ working-directory: contracts
+ run: npm ci --no-audit
+ - name: Check JS formatting
+ working-directory: contracts
+ run: npm run lint:js:check
+ - name: Run smart contracts tests
+ working-directory: contracts
+ run: make test
+ - name: Generate coverage report
+ working-directory: contracts
+ run: make coverage
+
+ solidity-format-check:
+ runs-on: ubuntu-latest
+ name: Solidity format check
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ ref: ${{ github.head_ref }}
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 18.15.0
+ cache-dependency-path: contracts/package-lock.json
+ - name: Npm install
+ working-directory: contracts
+ run: npm ci --no-audit
+ - name: Check Solidity formatting
+ working-directory: contracts
+ run: npm run fmt:sol:check
diff --git a/.github/workflows/sdk.yml b/.github/workflows/sdk.yml
new file mode 100644
index 000000000..b81877cf3
--- /dev/null
+++ b/.github/workflows/sdk.yml
@@ -0,0 +1,131 @@
+name: Sdk CI
+
+on:
+ push:
+ workflow_dispatch:
+ inputs:
+ e2e-tests-with-ssh:
+ description: Run end to end tests with ability to ssh into environment
+ required: false
+ type: boolean
+ default: false
+ e2e-tests-logs-dump:
+ description: Dump logs after running end to end tests
+ required: false
+ type: boolean
+ default: false
+
+jobs:
+ changes:
+ runs-on: ubuntu-latest
+ name: Filter commit changes
+ outputs:
+ sdk: ${{ steps.filter.outputs.sdk }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ - name: Filter commit changes
+ uses: dorny/paths-filter@v2
+ id: filter
+ with:
+ base: ${{ github.ref }}
+ list-files: "json"
+ filters: |
+ sdk:
+ - 'sdk/**'
+ - '.github/workflows/sdk.yml'
+ - '.github/workflows/reuse-*.yml'
+
+ store_image_name_and_tags:
+ uses: ./.github/workflows/reuse-store-image-name-and-tags.yml
+ with:
+ image_name: consensys/linea-postman
+
+ check_image_tags_exist:
+ needs: [ changes, store_image_name_and_tags ]
+ if: ${{ needs.changes.outputs.sdk == 'false' }}
+ uses: ./.github/workflows/reuse-check-image-tags-exist.yml
+ with:
+ last_commit_tag: ${{ needs.store_image_name_and_tags.outputs.last_commit_tag }}
+ common_ancestor_tag: ${{ needs.store_image_name_and_tags.outputs.common_ancestor_tag }}
+ image_name: ${{ needs.store_image_name_and_tags.outputs.image_name }}
+ secrets: inherit
+
+ sdk-tag-only:
+ needs: [ changes, store_image_name_and_tags, check_image_tags_exist ]
+ if: ${{ needs.changes.outputs.sdk == 'false' }}
+ uses: ./.github/workflows/reuse-image-tag-push.yml
+ with:
+ commit_tag: ${{ needs.store_image_name_and_tags.outputs.commit_tag }}
+ last_commit_tag: ${{ needs.store_image_name_and_tags.outputs.last_commit_tag }}
+ common_ancestor_tag: ${{ needs.store_image_name_and_tags.outputs.common_ancestor_tag }}
+ develop_tag: ${{ needs.store_image_name_and_tags.outputs.develop_tag }}
+ untested_tag_suffix: ${{ needs.store_image_name_and_tags.outputs.untested_tag_suffix }}
+ image_name: ${{ needs.store_image_name_and_tags.outputs.image_name }}
+ last_commit_tag_exists: ${{ needs.check_image_tags_exist.outputs.last_commit_tag_exists }}
+ common_ancestor_commit_tag_exists: ${{ needs.check_image_tags_exist.outputs.common_ancestor_commit_tag_exists }}
+ secrets: inherit
+
+ build-and-publish:
+ needs: [ changes, store_image_name_and_tags, sdk-tag-only ]
+ if: ${{ always() && (needs.changes.outputs.sdk == 'true' || needs.sdk-tag-only.result != 'success' || needs.sdk-tag-only.outputs.image_tagged != 'true') }}
+ runs-on: ubuntu-22.04
+ env:
+ COMMIT_TAG: ${{ needs.store_image_name_and_tags.outputs.commit_tag }}
+ DEVELOP_TAG: ${{ needs.store_image_name_and_tags.outputs.develop_tag }}
+ UNTESTED_TAG_SUFFIX: ${{ needs.store_image_name_and_tags.outputs.untested_tag_suffix }}
+ IMAGE_NAME: ${{ needs.store_image_name_and_tags.outputs.image_name }}
+ name: Postman build and push
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ ssh-key: ${{ secrets.SELF_GITHUB_SSH_KEY }}
+ submodules: true
+ persist-credentials: false
+ - name: Login to Docker Hub
+ uses: docker/login-action@v2
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v2
+ with:
+ platforms: 'arm64,arm'
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v2
+ - name: Show the "version" build argument
+ run: |
+ echo "We inject the commit tag in the docker image ${{ env.COMMIT_TAG }}"
+ echo COMMIT_TAG=${{ env.COMMIT_TAG }} >> $GITHUB_ENV
+ - name: Build and push postman image
+ uses: docker/build-push-action@v4
+ with:
+ context: ./sdk
+ file: ./sdk/Dockerfile
+ platforms: linux/amd64,linux/arm64
+ push: true
+ tags: |
+ ${{ env.IMAGE_NAME }}:${{ env.COMMIT_TAG }}-${{ env.UNTESTED_TAG_SUFFIX }}
+
+ run-e2e-tests:
+ needs: [ changes, store_image_name_and_tags, build-and-publish ]
+ if: ${{ always() && (needs.changes.outputs.coordinator == 'true' || needs.build-and-publish.result == 'success') }}
+ uses: ./.github/workflows/reuse-run-e2e-tests.yml
+ with:
+ commit_tag: ${{ needs.store_image_name_and_tags.outputs.commit_tag }}
+ untested_tag_suffix: ${{ needs.store_image_name_and_tags.outputs.untested_tag_suffix }}
+ e2e-tests-with-ssh: ${{ false && inputs.e2e-tests-with-ssh }}
+ e2e-tests-logs-dump: ${{ false && inputs.e2e-tests-logs-dump }}
+ secrets: inherit
+
+ tag-after-run-tests-success:
+ needs: [ store_image_name_and_tags, run-e2e-tests ]
+ if: ${{ always() && needs.run-e2e-tests.outputs.tests_outcome == 'success' }}
+ uses: ./.github/workflows/reuse-tag-without-untested-suffix.yml
+ with:
+ commit_tag: ${{ needs.store_image_name_and_tags.outputs.commit_tag }}
+ develop_tag: ${{ needs.store_image_name_and_tags.outputs.develop_tag }}
+ untested_tag_suffix: ${{ needs.store_image_name_and_tags.outputs.untested_tag_suffix }}
+ image_name: ${{ needs.store_image_name_and_tags.outputs.image_name }}
+ secrets: inherit
\ No newline at end of file
diff --git a/.github/workflows/security-report-to-csv.yml b/.github/workflows/security-report-to-csv.yml
new file mode 100644
index 000000000..470f2f551
--- /dev/null
+++ b/.github/workflows/security-report-to-csv.yml
@@ -0,0 +1,14 @@
+name: Export Security Report to CSV
+on: workflow_dispatch
+jobs:
+ data_gathering:
+ runs-on: ubuntu-20.04
+ steps:
+ - name: CSV export
+ uses: advanced-security/ghas-to-csv@v2
+ - name: Upload CSV
+ uses: actions/upload-artifact@v3
+ with:
+ name: ghas-data
+ path: ${{ github.workspace }}/*.csv
+ if-no-files-found: error
diff --git a/.github/workflows/traces-api-facade.yml b/.github/workflows/traces-api-facade.yml
new file mode 100644
index 000000000..93d1668dc
--- /dev/null
+++ b/.github/workflows/traces-api-facade.yml
@@ -0,0 +1,167 @@
+name: Traces-api-facade CI
+
+on:
+ push:
+ workflow_dispatch:
+ inputs:
+ coverage:
+ description: To generate test report
+ required: false
+ type: boolean
+ default: false
+ e2e-tests-with-ssh:
+ description: Run end to end tests with ability to ssh into environment
+ required: false
+ type: boolean
+ default: false
+ e2e-tests-logs-dump:
+ description: Dump logs after running end to end tests
+ required: false
+ type: boolean
+ default: false
+
+jobs:
+ changes:
+ runs-on: ubuntu-latest
+ name: Filter commit changes
+ outputs:
+ traces-api-facade: ${{ steps.filter.outputs.traces-api-facade }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ - name: Filter commit changes
+ uses: dorny/paths-filter@v2
+ id: filter
+ with:
+ base: ${{ github.ref }}
+ list-files: "json"
+ filters: |
+ traces-api-facade:
+ - 'traces-api-facade/**'
+ - 'jvm-libs/**'
+ - 'config/common/traces-limits-v1.toml'
+ - '.github/workflows/traces-api-facade.yml'
+ - '.github/workflows/reuse-*.yml'
+ - 'buildSrc/**'
+ - 'gradle/**'
+ - 'build.gradle'
+ - 'gradle.properties'
+ - 'settings.gradle'
+
+ store_image_name_and_tags:
+ uses: ./.github/workflows/reuse-store-image-name-and-tags.yml
+ with:
+ image_name: consensys/linea-traces-api-facade
+
+ check_image_tags_exist:
+ needs: [ changes, store_image_name_and_tags ]
+ if: ${{ needs.changes.outputs.traces-api-facade == 'false' }}
+ uses: ./.github/workflows/reuse-check-image-tags-exist.yml
+ with:
+ last_commit_tag: ${{ needs.store_image_name_and_tags.outputs.last_commit_tag }}
+ common_ancestor_tag: ${{ needs.store_image_name_and_tags.outputs.common_ancestor_tag }}
+ image_name: ${{ needs.store_image_name_and_tags.outputs.image_name }}
+ secrets: inherit
+
+ traces-api-facade-tag-only:
+ needs: [ changes, store_image_name_and_tags, check_image_tags_exist ]
+ if: ${{ needs.changes.outputs.traces-api-facade == 'false' }}
+ uses: ./.github/workflows/reuse-image-tag-push.yml
+ with:
+ commit_tag: ${{ needs.store_image_name_and_tags.outputs.commit_tag }}
+ last_commit_tag: ${{ needs.store_image_name_and_tags.outputs.last_commit_tag }}
+ common_ancestor_tag: ${{ needs.store_image_name_and_tags.outputs.common_ancestor_tag }}
+ develop_tag: ${{ needs.store_image_name_and_tags.outputs.develop_tag }}
+ untested_tag_suffix: ${{ needs.store_image_name_and_tags.outputs.untested_tag_suffix }}
+ image_name: ${{ needs.store_image_name_and_tags.outputs.image_name }}
+ last_commit_tag_exists: ${{ needs.check_image_tags_exist.outputs.last_commit_tag_exists }}
+ common_ancestor_commit_tag_exists: ${{ needs.check_image_tags_exist.outputs.common_ancestor_commit_tag_exists }}
+ secrets: inherit
+
+ build-and-publish:
+ needs: [ changes, store_image_name_and_tags, traces-api-facade-tag-only ]
+ if: ${{ always() && (needs.changes.outputs.traces-api-facade == 'true' || needs.traces-api-facade-tag-only.result != 'success' || needs.traces-api-facade-tag-only.outputs.image_tagged != 'true') }}
+ runs-on: ubuntu-latest
+ env:
+ COMMIT_TAG: ${{ needs.store_image_name_and_tags.outputs.commit_tag }}
+ DEVELOP_TAG: ${{ needs.store_image_name_and_tags.outputs.develop_tag }}
+ UNTESTED_TAG_SUFFIX: ${{ needs.store_image_name_and_tags.outputs.untested_tag_suffix }}
+ IMAGE_NAME: ${{ needs.store_image_name_and_tags.outputs.image_name }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ ref: ${{ github.head_ref }}
+ - name: Install gradle
+ uses: gradle/gradle-build-action@v2
+ - name: Run tests with coverage
+ uses: nick-fields/retry@v2
+ if: ${{ inputs.coverage }}
+ with:
+ max_attempts: 2
+ retry_on: error
+ timeout_minutes: 20
+ command: |
+ ./gradlew traces-api-facade:app:buildNeeded jacocoRootReport --no-daemon
+ - name: Run tests without coverage
+ uses: nick-fields/retry@v2
+ if: ${{ !inputs.coverage }}
+ with:
+ max_attempts: 2
+ retry_on: error
+ timeout_minutes: 20
+ command: |
+ ./gradlew traces-api-facade:app:buildNeeded --no-daemon
+ - name: Build dist
+ run: |
+ ./gradlew traces-api-facade:app:shadowJar
+ echo ${{ github.workspace }}
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v2
+ - name: Set up Docker Buildx
+ id: buildx
+ uses: docker/setup-buildx-action@v2
+ - name: Login to Docker Hub
+ if: github.event_name != 'pull_request'
+ uses: docker/login-action@v1
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+ - name: Docker meta
+ id: traces-api-facade
+ uses: docker/metadata-action@v3
+ with:
+ images: consensys/linea-traces-api-facade
+ - name: Build & push
+ uses: docker/build-push-action@v4
+ with:
+ context: .
+ build-contexts: jar=./traces-api-facade/app/build/libs/
+ file: ./traces-api-facade/Dockerfile
+ platforms: linux/amd64,linux/arm64
+ push: true
+ tags: |
+ ${{ env.IMAGE_NAME }}:${{ env.COMMIT_TAG }}-${{ env.UNTESTED_TAG_SUFFIX }}
+
+ run-e2e-tests:
+ needs: [ changes, store_image_name_and_tags, build-and-publish ]
+ if: ${{ always() && (needs.changes.outputs.coordinator == 'true' || needs.build-and-publish.result == 'success') }}
+ uses: ./.github/workflows/reuse-run-e2e-tests.yml
+ with:
+ commit_tag: ${{ needs.store_image_name_and_tags.outputs.commit_tag }}
+ untested_tag_suffix: ${{ needs.store_image_name_and_tags.outputs.untested_tag_suffix }}
+ e2e-tests-with-ssh: ${{ false && inputs.e2e-tests-with-ssh }}
+ e2e-tests-logs-dump: ${{ false && inputs.e2e-tests-logs-dump }}
+ secrets: inherit
+
+ tag-after-run-tests-success:
+ needs: [ store_image_name_and_tags, run-e2e-tests ]
+ if: ${{ always() && needs.run-e2e-tests.outputs.tests_outcome == 'success' }}
+ uses: ./.github/workflows/reuse-tag-without-untested-suffix.yml
+ with:
+ commit_tag: ${{ needs.store_image_name_and_tags.outputs.commit_tag }}
+ develop_tag: ${{ needs.store_image_name_and_tags.outputs.develop_tag }}
+ untested_tag_suffix: ${{ needs.store_image_name_and_tags.outputs.untested_tag_suffix }}
+ image_name: ${{ needs.store_image_name_and_tags.outputs.image_name }}
+ secrets: inherit
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..aade3e78d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,49 @@
+*.bak
+*.swp
+*.tmp
+*~.nib
+*.iml
+*.launch
+*.swp
+*.log
+*.jar
+.classpath
+.DS_Store
+.externalToolBuilders/
+.gradle/
+.idea/
+.vscode
+.loadpath
+.metadata
+.prefs
+.project
+.recommenders/
+.settings
+.springBeans
+.vertx
+.run/**.run.xml
+.envrc
+bin/
+target/
+tmp/
+build/
+out/
+node_modules/
+**/src/*/generated
+/prover/.direnv/
+/prover/.envrc
+/prover/testdata/
+/prover/zkevm/zkevm.bin
+**/.direnv
+typechain
+.env
+/sdk/.nyc_output/
+/sdk/scripts/contractAddresses.json
+/sdk/src/lib/**/*.js
+/sdk/scripts/*.js
+/sdk/postman.db
+/sdk/coverage
+dist
+contracts/.env~
+/prover/cmd/contract-gen/contract-gen
+/prover/cmd/contract-gen/docker/**
\ No newline at end of file
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 000000000..4858b4f17
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,6 @@
+[submodule "constraints"]
+ path = constraints
+ url = git@github.com:ConsenSys/zkevm-constraints.git
+[submodule "corset"]
+ path = corset
+ url = git@github.com:consensys/corset.git
diff --git a/.prettierrc.js b/.prettierrc.js
new file mode 100644
index 000000000..87bbd4e15
--- /dev/null
+++ b/.prettierrc.js
@@ -0,0 +1,5 @@
+module.exports = {
+ useTabs: false,
+ tabWidth: 2,
+ singleQuote: true,
+};
diff --git a/.run/Coordinator.run.xml.template b/.run/Coordinator.run.xml.template
new file mode 100644
index 000000000..bd1a418d7
--- /dev/null
+++ b/.run/Coordinator.run.xml.template
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.run/TracesApi.run.xml.template b/.run/TracesApi.run.xml.template
new file mode 100644
index 000000000..8c2a8e504
--- /dev/null
+++ b/.run/TracesApi.run.xml.template
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 000000000..a566e4738
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,2 @@
+This docker, code, and any intellectual property herein, is subject to our
+[Terms of Use](https://consensys.net/terms-of-use/) whilst we explore definitive open source licences
diff --git a/Makefile b/Makefile
new file mode 100644
index 000000000..a175f943b
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,204 @@
+dokcer-pull-develop:
+ docker compose -f docker/compose.yml --profile l2 pull
+
+clean-smc-folders:
+ rm -f contracts/.openzeppelin/unknown-31648428.json
+ rm -f contracts/.openzeppelin/unknown-1337.json
+
+clean-local-folders:
+ make clean-smc-folders
+ rm -rf tmp/local/*
+
+clean-testnet-folders:
+ make clean-smc-folders
+ rm -rf tmp/testnet/*
+
+clean-environment:
+ docker-compose -f docker/compose.yml --profile l1 --profile l2 --profile debug down || true
+ make clean-local-folders
+ docker network prune -f
+ docker volume rm linea-local-dev linea-logs || true # ignore failure if volumes do not exist already
+
+clean-environment-ci:
+ docker-compose -f docker/compose.yml -f docker/compose-ci.overrides.yml --profile l1 --profile l2 down || true
+ make clean-smc-folders
+ docker network prune -f
+ docker volume rm linea-local-dev linea-logs || true # ignore failure if volumes do not exist already
+
+start-l1:
+ docker-compose -f docker/compose.yml -f docker/compose-local-dev.overrides.yml --profile l1 up -d
+
+start-l1-ci:
+ docker-compose -f docker/compose.yml --profile l1 up -d
+
+start-l2:
+ docker-compose -f docker/compose.yml -f docker/compose-local-dev.overrides.yml --profile l2 up -d
+
+start-l2-ci:
+ docker-compose -f docker/compose.yml -f docker/compose-ci.overrides.yml --profile l2 up -d
+
+start-l2-blockchain-only:
+ docker-compose -f docker/compose.yml -f docker/compose-local-dev.overrides.yml --profile l2-bc up -d
+
+start-whole-environment:
+ docker-compose -f docker/compose.yml -f docker/compose-local-dev.overrides.yml --profile l1 --profile l2 --profile debug up -d
+
+start-whole-environment-ci:
+ docker-compose -f docker/compose.yml -f docker/compose-ci.overrides.yml --profile l1 --profile l2 up -d
+
+pull-all-images-ci:
+ docker-compose -f docker/compose.yml -f docker/compose-ci.overrides.yml --profile l1 --profile l2 pull
+
+deploy-zkevm2-to-local:
+ # WARNING: FOR LOCAL DEV ONLY - DO NOT REUSE THESE KEYS ELSEWHERE
+ cd contracts/; \
+ sed "s/BLOCKCHAIN_NODE=.*/BLOCKCHAIN_NODE=http:\/\/localhost:8445/" .env.template > .env; \
+ sed -i~ 's/PRIVATE_KEY=.*/PRIVATE_KEY=0xae99052fbea8f9c092f6d7c5132d585edc81aa1f11e4b1d18ac8fce0db44a078/' .env; \
+ npx hardhat run ./scripts/deployment/deployZkEVM.ts --network zkevm_dev
+
+deploy-zkevm2-to-ci:
+ cd contracts/; \
+ sed "s/BLOCKCHAIN_NODE=.*/BLOCKCHAIN_NODE=http:\/\/localhost:8445/" .env.template.ci > .env; \
+ npx hardhat run ./scripts/deployment/deployZkEVM.ts --network zkevm_dev
+
+deploy-l2messageservice-to-local:
+ # WARNING: FOR LOCAL DEV ONLY - DO NOT REUSE THESE KEYS ELSEWHERE
+ cd contracts/; \
+ sed "s/BLOCKCHAIN_NODE=.*/BLOCKCHAIN_NODE=http:\/\/localhost:8545/" .env.template > .env; \
+ sed -i~ 's/L2MSGSERVICE_L1L2_MESSAGE_SETTER=.*/L2MSGSERVICE_L1L2_MESSAGE_SETTER="0x1B9AbEeC3215D8AdE8a33607f2cF0f4F60e5F0D0"/' .env; \
+ sed -i~ 's/PRIVATE_KEY=.*/PRIVATE_KEY=0x1dd171cec7e2995408b5513004e8207fe88d6820aeff0d82463b3e41df251aae/' .env; \
+ npx hardhat run ./scripts/deployment/deployL2MessageService.ts --network zkevm_dev
+
+deploy-l2messageservice-to-ci:
+ # WARNING: FOR LOCAL DEV ONLY - DO NOT REUSE THESE KEYS ELSEWHERE
+ cd contracts/; \
+ sed "s/BLOCKCHAIN_NODE=.*/BLOCKCHAIN_NODE=http:\/\/localhost:8545/" .env.template.ci > .env; \
+ sed -i~ 's/L2MSGSERVICE_L1L2_MESSAGE_SETTER=.*/L2MSGSERVICE_L1L2_MESSAGE_SETTER="0x1B9AbEeC3215D8AdE8a33607f2cF0f4F60e5F0D0"/' .env; \
+ sed -i~ 's/PRIVATE_KEY=.*/PRIVATE_KEY=0x1dd171cec7e2995408b5513004e8207fe88d6820aeff0d82463b3e41df251aae/' .env; \
+ npx hardhat run ./scripts/deployment/deployL2MessageService.ts --network zkevm_dev
+
+upgrade-zkevm2-on-uat:
+ cd contracts/; \
+ rm -f .openzeppelin/goerli.json; \
+ sed "s/BLOCKCHAIN_NODE=.*/BLOCKCHAIN_NODE=https:\/\/goerli.infura.io\/v3\/${INFURA_KEY}/" .env.template.uat > .env; \
+ sed -i~ "s/PRIVATE_KEY=.*/PRIVATE_KEY=${PRIVATE_KEY}/" .env; \
+ npx hardhat run ./scripts/upgrades/upgradeZkEVMv2.ts --network zkevm_dev
+
+fresh-start-l2-blockchain-only:
+ make clean-environment
+ make start-l2-blockchain-only
+
+restart-shomei:
+ docker-compose -f docker/compose.yml -f docker/compose-local-dev.overrides.yml rm zkbesu-shomei shomei
+ rm -rf tmp/local/shomei/*
+ docker-compose -f docker/compose.yml -f docker/compose-local-dev.overrides.yml up zkbesu-shomei shomei -d
+
+
+fresh-start-all:
+ make clean-environment
+ make start-l1
+ make deploy-zkevm2-to-local
+ make start-whole-environment
+ make deploy-l2messageservice-to-local
+
+fresh-start-all-ci:
+ make clean-environment-ci
+ make start-l1-ci
+ make deploy-zkevm2-to-ci
+ make start-whole-environment-ci
+ make deploy-l2messageservice-to-ci
+
+start-all-ci:
+ make start-whole-environment-ci
+ make start-all-deploy-ci
+
+start-all-deploy-ci: start-deploy-l1-ci start-deploy-l2-ci
+
+start-deploy-l1-ci:
+ make deploy-zkevm2-to-ci
+
+start-deploy-l2-ci:
+ make deploy-l2messageservice-to-ci
+
+send-some-transactions-to-l2:
+ cd ../zkevm-deployment/smart_contract/scripts; \
+ # WARNING: FOR LOCAL DEV ONLY - DO NOT REUSE THESE KEYS ELSEWHERE
+ ./txHelper.js transfer --blockchainNode http://localhost:8845 --privKey 0x1dd171cec7e2995408b5513004e8207fe88d6820aeff0d82463b3e41df251aae --wei 1 --transfers 1 --mode WAITEXEC --maxFeePerGas 1000000000 --maxPriorityFeePerGas 1000000000
+
+testnet-start-l2:
+ docker-compose -f docker/compose.yml -f docker/compose-testnet-sync.overrides.yml --profile l2 up -d
+
+testnet-start-l2-traces-node-only:
+ docker-compose -f docker/compose.yml -f docker/compose-testnet-sync.overries.yml up traces-node -d
+
+testnet-start: start-l1 deploy-zkevm2-to-local testnet-start-l2
+testnet-restart-l2-keep-state:
+ docker-compose -f docker/compose.yml -f docker/compose-testnet-sync.overrides.yml rm -f -s -v sequencer traces-node coordinator
+ make testnet-start-l2
+
+testnet-restart-l2-clean-state:
+ docker-compose -f docker/compose.yml -f docker/compose-testnet-sync.overrides.yml rm -f -s -v sequencer traces-node coordinator
+ docker volume rm testnet-data
+ make clean-testnet-folders
+ make testnet-start-l2
+
+testnet-down:
+ docker-compose -f docker/compose.yml -f docker/compose-testnet-sync.overrides.yml --profile l1 --profile l2 down -v
+ make clean-testnet-folders
+
+zkgeth-sequencer-smoke-test:
+ make clean-environment
+ docker-compose -f docker/compose.yml -f docker/compose-local-dev.overrides.yml up sequencer -d
+ make deploy-l2messageservice-to-local-tmp
+
+stop_pid:
+ if [ -f $(PID_FILE) ]; then \
+ kill `cat $(PID_FILE)`; \
+ echo "Stopped process with PID `cat $(PID_FILE)`"; \
+ rm $(PID_FILE); \
+ else \
+ echo "$(PID_FILE) does not exist. No process to stop."; \
+ fi
+
+restart-l2-minimal-stack-local:
+ make stop-coordinator
+ make stop-traces-api
+ make stop_pid PID_FILE=tmp/local/traces-app.pid
+ make clean-environment
+ make start-l2
+ make deploy-l2messageservice-to-local
+ make start-traces-api
+ make start-coordinator
+ # TODO: use locally built prover for faster feedback loop
+
+stop-l2-minimal-stack-local:
+ make stop-coordinator
+ make stop-traces-api
+ make clean-environment
+
+start-coordinator:
+ mkdir -p tmp/local/logs
+ ./gradlew coordinator:app:run \
+ -Dconfig.override.testL1Disabled=true \
+ -Dconfig.override.traces.counters.endpoints="http://127.0.0.1:8081" \
+ -Dconfig.override.traces.conflation.endpoints="http://127.0.0.1:8081" \
+ -Dconfig.override.dynamic-gas-price-service.miner-gas-price-update-recipients="http://127.0.0.1:8545/,http://127.0.0.1:8645/" > tmp/local/logs/coordinator.log & echo "$$!" > tmp/local/coordinator.pid
+
+stop-coordinator:
+ make stop_pid PID_FILE=tmp/local/coordinator.pid
+
+restart-coordinator:
+ make stop-coordinator
+ make start-coordinator
+
+start-traces-api:
+ mkdir -p tmp/local/logs
+ mkdir -p tmp/local/traces/raw
+ ./gradlew traces-api:app:run > tmp/local/logs/traces-app.log & echo "$$!" > tmp/local/traces-app.pid
+
+stop-traces-api:
+ make stop_pid PID_FILE=tmp/local/traces-app.pid
+
+restart-traces-api:
+ make stop-traces-api
+ make start-traces-api
diff --git a/README.md b/README.md
new file mode 100644
index 000000000..123d3cf0b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,8 @@
+# Linea zkEVM
+This is a monorepo for Linea, the Consensys zkEVM network.
+
+Homepage: https://linea.build/
+Docs: https://docs.linea.build/
+Mirror.xyz: https://linea.mirror.xyz/
+Support: https://support.linea.build
+Twitter: https://twitter.com/LineaBuild
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 000000000..4eb467bbb
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,40 @@
+# Security Policy
+
+We consider the security of our systems a top priority. But no matter how much effort we put into system security, there can still be vulnerabilities present.
+
+## Reporting Security Issues
+
+**DO NOT** report security vulnerabilities through public GitHub issues. Instead, please use one of the following means of communications to report it to us:
+
+1. Report through our Linea [Immunefi program](https://immunefi.com/bounty/linea/) or
+2. Email us at [security-report@linea.build](mailto:security-report@linea.build) with details about the security issue.
+
+Please provide the following details in your email:
+
+- Description of the vulnerability
+- Steps to reproduce the vulnerability
+- Versions affected
+- Any potential mitigations or workarounds you've identified
+
+## Responsible Disclosure Security Policy
+
+A responsible disclosure policy helps protect users of the project from publicly disclosed security vulnerabilities without a fix by employing a process where vulnerabilities are first triaged in a private manner, and only publicly disclosed after a reasonable time period that allows patching the vulnerability and provides an upgrade path for users.
+
+When contacting us directly via email, we will do our best efforts to respond in a reasonable time to resolve the issue. When contacting a security program their disclosure policy will provide details on time-frame, processes and paid bounties.
+
+We kindly ask you to refrain from malicious acts that put our users, the project, or any of the project's team members at risk.
+
+### Risk Disclosures
+Linea risk disclosures can be found at:
+- Linea docs - [https://docs.linea.build/risk-disclosures](https://docs.linea.build/risk-disclosures)
+- Linea Immunefi program - [https://immunefi.com/bounty/linea/](https://immunefi.com/bounty/linea/)
+
+## Scope
+
+This security policy applies to the code, libraries, and configurations within this repository. This includes any code or components that are part of the repository or its dependencies.
+
+## Previous Audits
+
+- Plonk Verifier https://consensys.net/diligence/audits/private/re9fdlhtjn7jfr/
+- Message Service & Rollup: https://consensys.net/diligence/audits/private/zxi4edywq3d1zr/
+- Canonical Token Bridge: https://consensys.net/diligence/audits/private/nzqt1bai7j8ryf/
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 000000000..b4215109b
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,106 @@
+import org.gradle.api.tasks.testing.logging.TestLogEvent
+import org.gradle.api.tasks.testing.logging.TestExceptionFormat
+
+plugins {
+ id 'com.diffplug.spotless' version '6.16.0'
+ id("org.jetbrains.kotlin.jvm")
+}
+
+allprojects {
+ repositories { mavenCentral() }
+
+ ext {
+ versions = [
+ vertx : "4.4.2",
+ micrometer : "1.8.4",
+ tuweni : "2.3.1",
+ jackson : "2.14.2",
+ web3j : "4.9.8",
+ besu : "22.4.2",
+ teku : "23.1.1",
+ ktlint : "0.47.0",
+ picoli : "4.7.1",
+ hoplite : "2.7.4",
+ caffeine : "3.1.6",
+ netty : "4.1.92.Final",
+ kotlinxDatetime: "0.4.0",
+ test : [
+ "wiremock" : "2.29.0",
+ "restassured": "5.3.0"
+ ]
+ ]
+ }
+
+ apply plugin: 'org.jetbrains.kotlin.jvm'
+ apply plugin: 'com.diffplug.spotless'
+ apply plugin: 'jacoco'
+
+ spotless {
+ kotlin {
+ // by default the target is every '.kt' and '.kts` file in the java sourcesets
+ //ktfmt()
+ ktlint(versions.ktlint.toString()).setEditorConfigPath("$rootDir/.editorconfig")
+ }
+ }
+
+ tasks.withType(JavaCompile) {
+ options.deprecation = true
+ options.compilerArgs += [
+ '-parameters',
+ '-Xlint:unchecked',
+ // '-Xlint:cast', generating too much noise now with Protobuf
+ '-Xlint:rawtypes',
+ '-Xlint:overloads',
+ '-Xlint:divzero',
+ '-Xlint:finally',
+ '-Xlint:static',
+ '-Xlint:deprecation',
+ // '-Werror',
+ ]
+ options.encoding = 'UTF-8'
+ }
+
+ jacoco {
+ toolVersion = '0.8.7'
+ if (project.tasks.findByName('integrationTest')) {
+ applyTo integrationTest
+ }
+ if (project.tasks.findByName('acceptanceTest')) {
+ applyTo acceptanceTest
+ }
+ }
+
+ test {
+ finalizedBy jacocoTestReport
+ testLogging {
+ events TestLogEvent.FAILED,
+ TestLogEvent.SKIPPED,
+ TestLogEvent.STANDARD_ERROR
+ exceptionFormat TestExceptionFormat.FULL
+ showCauses true
+ showExceptions true
+ showStackTraces true
+ // set showStandardStreams if you need to see test logs
+ showStandardStreams false
+ }
+ }
+
+ jacocoTestReport {
+ dependsOn test
+ }
+}
+
+task jacocoRootReport(type: JacocoReport) {
+ additionalSourceDirs.from files(subprojects.sourceSets.main.allSource.srcDirs)
+ sourceDirectories.from files(subprojects.sourceSets.main.allSource.srcDirs)
+ classDirectories.from files(subprojects.sourceSets.main.output)
+
+ executionData.from fileTree(dir: '.', includes: ['**/jacoco/*.exec'])
+ reports {
+ xml.required = true
+ // xml.enabled = true FIXME: deprecated, breaking latest versions of gradle.
+ csv.required = true
+ html.destination file("build/reports/jacocoHtml")
+ }
+ onlyIf = { true }
+}
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
new file mode 100644
index 000000000..281acc05d
--- /dev/null
+++ b/buildSrc/build.gradle
@@ -0,0 +1,17 @@
+/*
+ * This file was generated by the Gradle 'init' task.
+ */
+
+plugins {
+ // Support convention plugins written in Groovy. Convention plugins are build scripts in 'src/main' that automatically become available as plugins in the main build.
+ id 'groovy-gradle-plugin'
+}
+
+repositories {
+ // Use the plugin portal to apply community plugins in convention plugins.
+ gradlePluginPortal()
+}
+
+dependencies {
+ implementation 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10'
+}
diff --git a/buildSrc/settings.gradle b/buildSrc/settings.gradle
new file mode 100644
index 000000000..3f67e420d
--- /dev/null
+++ b/buildSrc/settings.gradle
@@ -0,0 +1,7 @@
+/*
+ * This file was generated by the Gradle 'init' task.
+ *
+ * This settings file is used to specify which projects to include in your build-logic build.
+ */
+
+rootProject.name = 'buildSrc'
diff --git a/buildSrc/src/main/groovy/net.consensys.zkevm.kotlin-application-conventions.gradle b/buildSrc/src/main/groovy/net.consensys.zkevm.kotlin-application-conventions.gradle
new file mode 100644
index 000000000..5e3856ee0
--- /dev/null
+++ b/buildSrc/src/main/groovy/net.consensys.zkevm.kotlin-application-conventions.gradle
@@ -0,0 +1,11 @@
+/*
+ * This file was generated by the Gradle 'init' task.
+ */
+
+plugins {
+ // Apply the common convention plugin for shared build configuration between library and application projects.
+ id 'net.consensys.zkevm.kotlin-common-conventions'
+
+ // Apply the application plugin to add support for building a CLI application in Java.
+ id 'application'
+}
diff --git a/buildSrc/src/main/groovy/net.consensys.zkevm.kotlin-common-conventions.gradle b/buildSrc/src/main/groovy/net.consensys.zkevm.kotlin-common-conventions.gradle
new file mode 100644
index 000000000..5900a2511
--- /dev/null
+++ b/buildSrc/src/main/groovy/net.consensys.zkevm.kotlin-common-conventions.gradle
@@ -0,0 +1,50 @@
+plugins {
+ id 'net.consensys.zkevm.kotlin-common-minimal-conventions'
+}
+
+repositories {
+ // Use Maven Central for resolving dependencies.
+ mavenCentral()
+ maven {
+ url 'https://artifacts.consensys.net/public/teku/maven/'
+ content { includeGroupByRegex('tech\\.pegasys($|\\..*)') }
+ }
+ maven {
+ url "https://hyperledger.jfrog.io/artifactory/besu-maven/"
+ content { includeGroupByRegex('org\\.hyperledger\\.besu($|\\..*)') }
+ }
+ maven {
+ url "https://artifacts.consensys.net/public/maven/maven/"
+ }
+// maven {
+// url "https://dl.cloudsmith.io/public/libp2p/jvm-libp2p/maven/"
+// content { includeGroupByRegex('io\\.libp2p($|\\..*)') }
+// }
+}
+
+dependencies {
+ constraints {
+ // Define dependency versions as constraints
+ implementation 'org.apache.commons:commons-text:1.9'
+ }
+ //
+ implementation 'commons-codec:commons-codec:1.15'
+ implementation 'com.michael-bull.kotlin-result:kotlin-result:1.1.16'
+ implementation "tech.pegasys.teku.internal:bytes:23.1.1"
+ implementation 'tech.pegasys.teku.internal:async:23.1.1'
+ implementation 'org.apache.tuweni:tuweni-units:2.3.1'
+ //
+
+ //
+ implementation 'org.apache.logging.log4j:log4j-api:2.20.0'
+ implementation 'org.apache.logging.log4j:log4j-core:2.20.0'
+ //
+
+ //
+ //
+}
+
+tasks.named('test') {
+ // Use JUnit Platform for unit tests.
+ useJUnitPlatform()
+}
diff --git a/buildSrc/src/main/groovy/net.consensys.zkevm.kotlin-common-minimal-conventions.gradle b/buildSrc/src/main/groovy/net.consensys.zkevm.kotlin-common-minimal-conventions.gradle
new file mode 100644
index 000000000..d2f20d25b
--- /dev/null
+++ b/buildSrc/src/main/groovy/net.consensys.zkevm.kotlin-common-minimal-conventions.gradle
@@ -0,0 +1,33 @@
+plugins {
+ // Apply the org.jetbrains.kotlin.jvm Plugin to add support for Kotlin.
+ id 'org.jetbrains.kotlin.jvm'
+ id 'idea'
+}
+
+java {
+ toolchain {
+ languageVersion = JavaLanguageVersion.of(17)
+ }
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ //
+ testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.2'
+ testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'
+ testImplementation 'org.assertj:assertj-core:3.24.2'
+ testImplementation 'org.mockito:mockito-core:5.1.1'
+ // TODO: org.mockito.kotlin:mockito-kotlin uses org.mockito:mockito-core:4.5.1,
+ // check later on if this may raise incompatibilities
+ testImplementation 'org.mockito.kotlin:mockito-kotlin:5.0.0'
+ testImplementation 'org.awaitility:awaitility:4.2.0'
+ //
+}
+
+tasks.named('test') {
+ // Use JUnit Platform for unit tests.
+ useJUnitPlatform()
+}
diff --git a/buildSrc/src/main/groovy/net.consensys.zkevm.kotlin-library-conventions.gradle b/buildSrc/src/main/groovy/net.consensys.zkevm.kotlin-library-conventions.gradle
new file mode 100644
index 000000000..637008eac
--- /dev/null
+++ b/buildSrc/src/main/groovy/net.consensys.zkevm.kotlin-library-conventions.gradle
@@ -0,0 +1,7 @@
+plugins {
+ // Apply the common convention plugin for shared build configuration between library and application projects.
+ id 'net.consensys.zkevm.kotlin-common-conventions'
+
+ // Apply the java-library plugin for API and implementation separation.
+ id 'java-library'
+}
diff --git a/config/README.md b/config/README.md
new file mode 100644
index 000000000..118bb8621
--- /dev/null
+++ b/config/README.md
@@ -0,0 +1 @@
+This folder holds example of configuration files linea components for local development, and CI.
diff --git a/config/blockscout/l1-blockscout.env b/config/blockscout/l1-blockscout.env
new file mode 100644
index 000000000..81e96e3e1
--- /dev/null
+++ b/config/blockscout/l1-blockscout.env
@@ -0,0 +1,105 @@
+# DOC of ENV https://docs.blockscout.com/for-developers/information-and-settings/env-variables
+# DOCKER_TAG=
+ETHEREUM_JSONRPC_VARIANT=geth
+NETWORK=Ethereum
+SUBNETWORK="Goerli Local Dev Mimic Net"
+LOGO=/images/blockscout_logo.svg
+LOGO_FOOTER=/images/blockscout_logo.svg
+# ETHEREUM_JSONRPC_WS_URL=
+ETHEREUM_JSONRPC_TRANSPORT=http
+ETHEREUM_JSONRPC_DISABLE_ARCHIVE_BALANCES=false
+IPC_PATH=
+NETWORK_PATH=/
+API_PATH=/
+SOCKET_ROOT=/
+# Host for API endpoint examples
+# BLOCKSCOUT_HOST=blockscout.consensys-zkevm.infura.io
+BLOCKSCOUT_PROTOCOL=http #use https for prod
+# SECRET_KEY_BASE=
+# CHECK_ORIGIN=
+PORT=4000
+COIN=GOERLI-ETH
+COIN_NAME="Goerli Eth"
+# METADATA_CONTRACT=
+# VALIDATORS_CONTRACT=
+# KEYS_MANAGER_CONTRACT=
+# REWARDS_CONTRACT=
+# TOKEN_BRIDGE_CONTRACT=
+# Regarding doc, these fields are meant for PoA networks.
+# However, no difference can be observed in Webpage
+# EMISSION_FORMAT=POA
+CHAIN_SPEC_PATH=/app/genesis.json
+POOL_SIZE=20
+POOL_SIZE_API=5
+ECTO_USE_SSL=false # SSL to Postgres, set to true in PROD if possible
+# Production environment variable to restart the application in the event of a crash.
+# HEART_BEAT_TIMEOUT=30
+# HEART_COMMAND=
+# BLOCKSCOUT_VERSION=
+# RELEASE_LINK=
+BLOCK_TRANSFORMER=clique
+# GRAPHIQL_TRANSACTION=
+LINK_TO_OTHER_EXPLORERS=false
+OTHER_EXPLORERS={}
+SUPPORTED_CHAINS={}
+CACHE_BLOCK_COUNT_PERIOD=7200
+CACHE_TXS_COUNT_PERIOD=7200
+CACHE_ADDRESS_COUNT_PERIOD=7200
+CACHE_ADDRESS_SUM_PERIOD=3600
+CACHE_TOTAL_GAS_USAGE_PERIOD=3600
+CACHE_ADDRESS_TRANSACTIONS_GAS_USAGE_COUNTER_PERIOD=1800
+CACHE_TOKEN_HOLDERS_COUNTER_PERIOD=3600
+CACHE_TOKEN_TRANSFERS_COUNTER_PERIOD=3600
+CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL=1800
+CACHE_AVERAGE_BLOCK_PERIOD=1800
+CACHE_MARKET_HISTORY_PERIOD=21600
+CACHE_ADDRESS_TRANSACTIONS_COUNTER_PERIOD=1800
+CACHE_ADDRESS_TOKENS_USD_SUM_PERIOD=1800
+CACHE_ADDRESS_TOKEN_TRANSFERS_COUNTER_PERIOD=1800
+CACHE_BRIDGE_MARKET_CAP_UPDATE_INTERVAL=1800
+CACHE_TOKEN_EXCHANGE_RATE_PERIOD=1800
+TOKEN_METADATA_UPDATE_INTERVAL=172800
+# Should we allow london onwards only?
+ALLOWED_EVM_VERSIONS=homestead,tangerineWhistle,spuriousDragon,byzantium,constantinople,petersburg,istanbul,berlin,london,default
+UNCLES_IN_AVERAGE_BLOCK_TIME=false
+DISABLE_INDEXER=false
+DISABLE_REALTIME_INDEXER=false
+DISABLE_TOKEN_INSTANCE_FETCHER=false
+INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER=false
+INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER=false
+# INDEXER_CATCHUP_BLOCKS_BATCH_SIZE=10
+# INDEXER_CATCHUP_BLOCKS_CONCURRENCY=10
+# WEBAPP_URL=
+# API_URL=
+WOBSERVER_ENABLED=false
+SHOW_ADDRESS_MARKETCAP_PERCENTAGE=true
+CHECKSUM_ADDRESS_HASHES=true
+CHECKSUM_FUNCTION=eth
+DISABLE_EXCHANGE_RATES=true
+DISABLE_KNOWN_TOKENS=false
+ENABLE_TXS_STATS=true
+SHOW_PRICE_CHART=false
+SHOW_TXS_CHART=true
+HISTORY_FETCH_INTERVAL=1
+TXS_HISTORIAN_INIT_LAG=0
+# Number of days for fetching of history of txs count per day
+# in order to display it in txs count per day history chart on the main page.
+TXS_STATS_DAYS_TO_COMPILE_AT_INIT=10
+COIN_BALANCE_HISTORY_DAYS=90
+APPS_MENU=false
+# GAS_PRICE=
+CHAIN_ID=19940131
+MAX_SIZE_UNLESS_HIDE_ARRAY=50
+HIDE_BLOCK_MINER=false
+DISPLAY_TOKEN_ICONS=false
+MAX_STRING_LENGTH_WITHOUT_TRIMMING=2040
+API_RATE_LIMIT=50
+API_RATE_LIMIT_BY_KEY=50
+API_RATE_LIMIT_BY_IP=50
+API_RATE_LIMIT_WHITELISTED_IPS=
+API_RATE_LIMIT_STATIC_API_KEY=
+FETCH_REWARDS_WAY=trace_block
+ENABLE_RUST_VERIFICATION_SERVICE=false
+# RUST_VERIFICATION_SERVICE_URL=http://host.docker.internal:8043/ not implemented for v0.1
+# DATABASE_READ_ONLY_API_URL=
+ACCOUNT_ENABLED=false
diff --git a/config/blockscout/l2-blockscout.env b/config/blockscout/l2-blockscout.env
new file mode 100644
index 000000000..ade81aa07
--- /dev/null
+++ b/config/blockscout/l2-blockscout.env
@@ -0,0 +1,80 @@
+# DOC of ENV https://docs.blockscout.com/for-developers/information-and-settings/env-variables
+# DOCKER_TAG=
+ETHEREUM_JSONRPC_VARIANT=geth
+NETWORK=Ethereum
+SUBNETWORK="Local Linea"
+LOGO=/images/blockscout_logo.svg
+LOGO_FOOTER=/images/blockscout_logo.svg
+# ETHEREUM_JSONRPC_WS_URL=
+ETHEREUM_JSONRPC_TRANSPORT=http
+ETHEREUM_JSONRPC_DISABLE_ARCHIVE_BALANCES=false
+IPC_PATH=
+NETWORK_PATH=/
+API_PATH=/
+SOCKET_ROOT=/
+BLOCKSCOUT_PROTOCOL=http #use https for prod
+PORT=4000
+COIN=GOERLI-ETH
+COIN_NAME="Goerli Eth"
+CHAIN_SPEC_PATH=/app/genesis.json
+POOL_SIZE=20
+POOL_SIZE_API=5
+ECTO_USE_SSL=false # SSL to Postgres, set to true in PROD if possible
+BLOCK_TRANSFORMER=clique
+LINK_TO_OTHER_EXPLORERS=false
+OTHER_EXPLORERS={}
+SUPPORTED_CHAINS={}
+CACHE_BLOCK_COUNT_PERIOD=7200
+CACHE_TXS_COUNT_PERIOD=7200
+CACHE_ADDRESS_COUNT_PERIOD=7200
+CACHE_ADDRESS_SUM_PERIOD=3600
+CACHE_TOTAL_GAS_USAGE_PERIOD=3600
+CACHE_ADDRESS_TRANSACTIONS_GAS_USAGE_COUNTER_PERIOD=1800
+CACHE_TOKEN_HOLDERS_COUNTER_PERIOD=3600
+CACHE_TOKEN_TRANSFERS_COUNTER_PERIOD=3600
+CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL=1800
+CACHE_AVERAGE_BLOCK_PERIOD=1800
+CACHE_MARKET_HISTORY_PERIOD=21600
+CACHE_ADDRESS_TRANSACTIONS_COUNTER_PERIOD=1800
+CACHE_ADDRESS_TOKENS_USD_SUM_PERIOD=1800
+CACHE_ADDRESS_TOKEN_TRANSFERS_COUNTER_PERIOD=1800
+CACHE_BRIDGE_MARKET_CAP_UPDATE_INTERVAL=1800
+CACHE_TOKEN_EXCHANGE_RATE_PERIOD=1800
+TOKEN_METADATA_UPDATE_INTERVAL=172800
+ALLOWED_EVM_VERSIONS=homestead,tangerineWhistle,spuriousDragon,byzantium,constantinople,petersburg,istanbul,berlin,london,default
+UNCLES_IN_AVERAGE_BLOCK_TIME=false
+DISABLE_INDEXER=false
+DISABLE_REALTIME_INDEXER=false
+DISABLE_TOKEN_INSTANCE_FETCHER=false
+INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER=false
+INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER=false
+WOBSERVER_ENABLED=false
+SHOW_ADDRESS_MARKETCAP_PERCENTAGE=true
+CHECKSUM_ADDRESS_HASHES=true
+CHECKSUM_FUNCTION=eth
+DISABLE_EXCHANGE_RATES=true
+DISABLE_KNOWN_TOKENS=false
+ENABLE_TXS_STATS=true
+SHOW_PRICE_CHART=false
+SHOW_TXS_CHART=true
+HISTORY_FETCH_INTERVAL=1
+TXS_HISTORIAN_INIT_LAG=0
+TXS_STATS_DAYS_TO_COMPILE_AT_INIT=10
+COIN_BALANCE_HISTORY_DAYS=90
+APPS_MENU=false
+CHAIN_ID=59140
+MAX_SIZE_UNLESS_HIDE_ARRAY=50
+HIDE_BLOCK_MINER=false
+DISPLAY_TOKEN_ICONS=false
+MAX_STRING_LENGTH_WITHOUT_TRIMMING=2040
+API_RATE_LIMIT=50
+API_RATE_LIMIT_BY_KEY=50
+API_RATE_LIMIT_BY_IP=50
+API_RATE_LIMIT_WHITELISTED_IPS=
+API_RATE_LIMIT_STATIC_API_KEY=
+FETCH_REWARDS_WAY=trace_block
+ENABLE_RUST_VERIFICATION_SERVICE=false
+ACCOUNT_ENABLED=false
+
+FOOTER_GITHUB_LINK="https://github.com/ConsenSys"
+SHOW_TESTNET_LABEL="true"
\ No newline at end of file
diff --git a/config/common/traces-limits-v1.toml b/config/common/traces-limits-v1.toml
new file mode 100644
index 000000000..7b70da438
--- /dev/null
+++ b/config/common/traces-limits-v1.toml
@@ -0,0 +1,55 @@
+##
+# This file specifies prover limit by each EVM module
+# WARN: The prover/arithmetization team has the owneship of this.
+# Changing this values may compromise the system.
+# issue: https://github.com/ConsenSys/zkevm-monorepo/issues/525
+##
+
+[traces-limits]
+#
+# Arithmetization module limits
+#
+ADD = 262144
+BIN = 262144
+BIN_RT = 262144
+EC_DATA = 4096
+EXT = 16384
+HUB = 2097152
+INSTRUCTION_DECODER = 512 # Ugly hack, TODO: @franklin
+MMIO = 1048576
+MMU = 524288
+MMU_ID = 256
+MOD = 131072
+MUL = 65536
+MXP = 524288
+PHONEY_RLP = 65536 # can probably get lower
+PUB_HASH = 32768
+PUB_HASH_INFO = 8192
+PUB_LOG = 16384
+PUB_LOG_INFO = 16384
+RLP = 128
+ROM = 1048576
+SHF = 65536
+SHF_RT = 262144
+TX_RLP = 65536 # TODO: based on PhoneyRLP
+WCP = 262144
+
+#
+# Block-specific limits
+#
+BLOCK_TX = 200 # max number of tx in an L2 block
+BLOCK_L2L1LOGS = 16
+BLOCK_KECCAK = 8192
+
+#
+# Precompiles limits
+#
+PRECOMPILE_ECRECOVER = 10000
+PRECOMPILE_SHA2 = 10000
+PRECOMPILE_RIPEMD = 10000
+PRECOMPILE_IDENTITY = 10000
+PRECOMPILE_MODEXP = 10000
+PRECOMPILE_ECADD = 10000
+PRECOMPILE_ECMUL = 10000
+PRECOMPILE_ECPAIRING = 10000
+PRECOMPILE_BLAKE2F = 512
diff --git a/config/coordinator/coordinator-docker-testnet.config.overrides.toml b/config/coordinator/coordinator-docker-testnet.config.overrides.toml
new file mode 100644
index 000000000..5ac2075c1
--- /dev/null
+++ b/config/coordinator/coordinator-docker-testnet.config.overrides.toml
@@ -0,0 +1,26 @@
+# Can override any of this propeties in CLI as follows:
+# -Dconfig.override.sequencer.engine-api=http://127.0.0.1:8650
+
+[sequencer]
+version="0.0.1"
+engine-api="http://traces-node:8550"
+eth-api="http://traces-node:8545"
+
+[zk-geth-traces]
+eth-api = "http://traces-node:8545"
+new-block-polling-interval="PT0S"
+
+# Config of Traces API Facade endpoint
+[traces]
+endpoints=["http://traces-api:8080/"]
+[traces.file-manager]
+polling-interval="PT0S"
+
+[conflation]
+# Will force conflation to start a given block
+# This is for Dev/Testing purposes
+# alternatively can use -Dconfig.override.conflation.force-starting-block=1
+force-starting-block=5000
+
+[l1]
+disabled=true
diff --git a/config/coordinator/coordinator-docker-web3signer-override.config.toml b/config/coordinator/coordinator-docker-web3signer-override.config.toml
new file mode 100644
index 000000000..2ac720f30
--- /dev/null
+++ b/config/coordinator/coordinator-docker-web3signer-override.config.toml
@@ -0,0 +1,9 @@
+[l1-signer]
+# Web3j/Web3signer
+type="Web3Signer"
+
+[l2-signer]
+# Web3j/Web3signer
+type="Web3Signer"
+
+
diff --git a/config/coordinator/coordinator-docker.config.toml b/config/coordinator/coordinator-docker.config.toml
new file mode 100644
index 000000000..2992b4731
--- /dev/null
+++ b/config/coordinator/coordinator-docker.config.toml
@@ -0,0 +1,190 @@
+testL1Disabled = false
+
+[sequencer]
+eth-api="http://sequencer:8545"
+#version="0.0.1"
+#engine-api="http://sequencer:8550"
+#suggested-fee-recipient="0x0000000000000000000000000000000000000000"
+#block-time="PT6S"
+#jwt-secret-file="/var/lib/coordinator/jwt-secret.hex"
+
+[prover]
+version="0.2.0"
+# only File System supported for now. JSON-RPC will be added later
+# client-mode="file-system",
+fs-input-directory="/data/prover/request"
+fs-output-directory="/data/prover/response"
+fs-polling-interval="PT20S"
+fs-inprogess-proving-suffix-pattern="\\.inprogress\\.prover.*"
+timeout="PT10M"
+
+[zk-geth-traces]
+eth-api = "http://traces-node:8545"
+new-block-polling-interval="PT1S"
+
+[traces]
+version="0.2.0"
+[traces.counters]
+endpoints=["http://traces-api:8080/"]
+request-limit-per-endpoint=20
+request-max-retries=2
+request-retry-interval="PT1S"
+[traces.conflation]
+endpoints=["http://traces-api:8080/"]
+request-limit-per-endpoint=2
+request-max-retries=2
+request-retry-interval="PT1S"
+
+[traces.file-manager]
+traces-file-extension = "json.gz"
+raw-traces-directory = "/data/traces/raw"
+non-canonical-raw-traces-directory = "/data/traces/raw-non-canonical"
+create-non-canonical-directory = true
+polling-interval="PT1S"
+traces-file-creation-wait-timeout="PT30S"
+
+[state-manager]
+version="1.2.0"
+endpoints=["http://shomei:8888/"]
+request-limit-per-endpoint=3
+
+[api]
+observability_port = 9545
+
+[l1]
+rpc-endpoint="http://l1-validator:8545"
+zk-evm-contract-address="0xC737F2334651ea85A72D8DA9d933c821A8377F9f"
+finalization-polling-interval="PT6S"
+new-batch-polling-interval="PT6S"
+# blocks-to-finalization=2 is a dev value.
+blocks-to-finalization=2
+gas-limit=10000000
+fee-history-block-count=10
+fee-history-reward-percentile=15
+max-fee-per-gas=100000000000
+# blocks are 12s, this may catch in between blocks - could be 12?
+send-message-event-polling-interval="PT6S"
+# 10 blocks worth at 12s per block
+max-event-scraping-time="PT02M"
+block-range-loop-limit=5
+max-messages-to-collect=1000
+finalized-block-tag="latest"
+# reset this once we know what to do on dev/UAT
+earliest-block=0
+
+[l2]
+message-service-address="0xe537D669CA013d86EBeF1D64e40fC74CADC91987"
+gas-limit=10000000
+max-fee-per-gas-cap=100000000000
+fee-history-block-count=4
+fee-history-reward-percentile=15
+last-hash-search-window=25
+last-hash-search-max-blocks-back=1000
+anchoring-receipt-polling-interval="PT01S"
+max-receipt-retries=120
+# Number of children blocks to wait before considering a block "finalized"
+# and elegible for conflation and
+# this a workaround to mitigate Geth fork issues with Clique PoA
+# Coordinator will consider block as finalized after being included in the chain wtih children blocks-to-finalization
+# Recommended minimum of 2
+blocks-to-finalization=2
+
+[batch-submission]
+disabled=false
+max-batches-to-send-per-tick=10
+# Delay interval since
+proof-submission-delay="PT1S"
+
+[l1-signer]
+# Web3j/Web3signer
+type="Web3j"
+
+[l1-signer.web3j]
+private-key="0x202454d1b4e72c41ebf58150030f649648d3cf5590297fb6718e27039ed9c86d"
+
+[l1-signer.web3signer]
+endpoint="http://127.0.0.1:9000"
+max-pool-size=10
+keep-alive=true
+public-key="0x3c753c0c9db5ce651144dbba3da47476ddede5b98607272de97a347d72c2eac2be3f6aea33598f297a0cd0f12cf08fe0e5198531393ea726a36efd07a7872382"
+
+[l2-signer]
+# Web3j/Web3signer
+type="Web3j"
+
+[l2-signer.web3j]
+private-key="0x1dd171cec7e2995408b5513004e8207fe88d6820aeff0d82463b3e41df251aae"
+
+[l2-signer.web3signer]
+endpoint="http://127.0.0.1:9000"
+max-pool-size=10
+keep-alive=true
+public-key="0cdc73b5a30ecb3eb2028fdd2d5ab423221763fbda7127e38d664b033455e30e7c6833bff6b99197de4d2069de30f6aaa90626986b737b7e74e635f4f7cedfbf"
+
+[message-anchoring-service]
+disabled=false
+polling-interval="PT48S"
+max-messages-to-anchor=100
+
+[dynamic-gas-price-service]
+disabled=false
+polling-interval="PT12S"
+fee-history-block-count=50
+fee-history-reward-percentile=15
+base-fee-coefficient=0.1
+priority-fee-coefficient=1.0
+gas-price-cap=10000000000 # 10 GWEI
+miner-gas-price-update-recipients=[
+ "http://sequencer:8545/",
+ "http://traces-node:8545/",
+ "http://l2-node:8545/"
+]
+
+[conflation]
+#blocks-limit=2
+conflation-deadline="PT6S" # =3*l2_block_time
+conflation-deadline-check-interval="PT3S"
+conflation-deadline-last-block-confirmation-delay="PT2S" # recommended: at least 2 * blockInterval
+
+# Total number of bytes that can be used for blocks conflation;
+# This should be around:
+# L1_transaction_size_limit(128KB)
+# - (signature and rlp overhead) 300B margin, for now
+# - proof size: 928 + 2
+# - proof type: 32 + 1
+# - blockDataOffsetEncodingSize (BlockData[] rlp overhead): 2bytes
+#
+# Finalize Block call with 1 block takes: ~1926
+# (includes: proof 928, parentRoot: 32, verifierIndex: 4, gasPrice 32 and gasLimit 32
+# 1 block with:( 1tx, 1 l2ToL1MsgHashes, 1 fromAddresses, 1 batchReceptionIndices ))
+# total: 128*1024 - 2000 = 129072
+total-limit-bytes=129072
+
+# Number of extra bytes each block will require to send to L1, due smart contract interface;
+# This should be around:
+# blockRootHash: 32 + 1 bytes
+# l2BlockTimestamp: 4 + 1 bytes
+# BlockData struct rlp overhead: 2 bytes
+# total: 40 bytes
+per-block-overhead-bytes=40
+
+# Minimum number of bytes the smallest block with single ETH transfer would require;
+# If totalAccumulatedData + minBlockL1Size > totalLimitBytes, then conflation will be triggered
+# Setting for overhead atm
+min-block-l1-size-bytes=1699
+
+# This is to prevent inflight trasactions that may change Smart contract state while coordinator is restarted.
+# Queries SMC for last finalised block, and keeps polling until this number of blocks observe the same state.
+# If state is updated meanwhile, it resets counter and restarts the polling.
+consistent-number-of-blocks-on-l1-to-wait=1
+
+[database]
+host = "postgres"
+port = "5432"
+username = "postgres"
+password = "postgres"
+schema = "linea_coordinator"
+read_pool_size = 10
+read_pipelining_limit = 10
+transactional_pool_size = 10
+
diff --git a/config/coordinator/coordinator-local-dev.config.overrides.toml b/config/coordinator/coordinator-local-dev.config.overrides.toml
new file mode 100644
index 000000000..735d02887
--- /dev/null
+++ b/config/coordinator/coordinator-local-dev.config.overrides.toml
@@ -0,0 +1,50 @@
+# Can override any of this propeties in CLI as follows:
+# -Dconfig.override.sequencer.engine-api=http://127.0.0.1:8650
+
+[sequencer]
+eth-api="http://127.0.0.1:8645"
+
+[prover]
+fs-input-directory="tmp/local/prover/request"
+fs-output-directory="tmp/local/prover/response"
+
+[zk-geth-traces]
+eth-api = "http://127.0.0.1:8645"
+
+# Config of Traces API Facade endpoint
+[traces]
+[traces.counters]
+endpoints=["http://127.0.0.1:8080/"]
+[traces.conflation]
+endpoints=["http://127.0.0.1:8080/"]
+[traces.file-manager]
+traces-file-extension = "json.gz"
+raw-traces-directory = "tmp/local/traces/raw"
+non-canonical-raw-traces-directory = "tmp/local/traces/raw-non-canonical"
+
+[state-manager]
+endpoints=["http://127.0.0.1:8888/"]
+
+[dynamic-gas-price-service]
+#miner-gas-price-update-recipients=[]
+
+# [conflation]
+# blocks-limit=2
+# Will force conflation to start a given block
+# This is for Dev/Testing purposes
+# alternatively can use -Dconfig.override.conflation.force-starting-block=1
+#force-starting-block=1
+
+[l1]
+rpc-endpoint="http://127.0.0.1:8445"
+blocks-to-finalization=2
+earliestBlock=0
+
+[l2]
+blocks-to-finalization=0
+
+[database]
+host="localhost"
+
+[api]
+observability_port=9546
diff --git a/config/coordinator/log4j2-dev.xml b/config/coordinator/log4j2-dev.xml
new file mode 100644
index 000000000..0baf907d3
--- /dev/null
+++ b/config/coordinator/log4j2-dev.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/config/coordinator/vertx.properties b/config/coordinator/vertx.properties
new file mode 100644
index 000000000..533f2e60a
--- /dev/null
+++ b/config/coordinator/vertx.properties
@@ -0,0 +1,21 @@
+# set -Dvertx.configurationFile=/path/to/vertx.properties
+# Vert.x configuration file
+
+# Event loop thread pool size
+# eventLoopPoolSize=10
+
+# Worker thread pool size
+# workerPoolSize=20
+
+# internalBlockingPoolSize=20
+
+# Event loop timeout (in milliseconds) before a blocked thread warning is issued
+warnEventLoopBlocked=5000
+
+# Maximum number of event loop execution iterations before a blocked warning is issued
+maxEventLoopExecuteTime=5000
+maxEventLoopExecuteTimeUnit=MILLISECONDS
+maxWorkerExecuteTime=130
+maxWorkerExecuteTimeUnit=SECONDS
+logStacktraceThreshold=500
+preferNativeTransport=true
diff --git a/config/coordinator/vertx.template.config.properties b/config/coordinator/vertx.template.config.properties
new file mode 100644
index 000000000..87d10a217
--- /dev/null
+++ b/config/coordinator/vertx.template.config.properties
@@ -0,0 +1,115 @@
+blockedThreadCheckInterval=1000
+blockedThreadCheckIntervalUnit=MILLISECONDS
+maxEventLoopExecuteTime=2000000000
+maxEventLoopExecuteTimeUnit=NANOSECONDS
+maxWorkerExecuteTime=60000000000
+maxWorkerExecuteTimeUnit=NANOSECONDS
+warningExceptionTime=5000000000
+warningExceptionTimeUnit=NANOSECONDS
+eventLoopPoolSize=16
+internalBlockingPoolSize=20
+preferNativeTransport=true
+quorumSize=1
+useDaemonThread=
+workerPoolSize=20
+disableTCCL=
+haEnabled=
+haGroup=__DEFAULT__
+addressResolverOptions.cacheMaxTimeToLive=2147483647
+addressResolverOptions.cacheMinTimeToLive=
+addressResolverOptions.cacheNegativeTimeToLive=
+addressResolverOptions.maxQueries=4
+addressResolverOptions.ndots=1
+addressResolverOptions.optResourceEnabled=
+addressResolverOptions.queryTimeout=5000
+addressResolverOptions.rdFlag=true
+addressResolverOptions.rotateServers=
+addressResolverOptions.roundRobinInetAddress=
+eventBusOptions.acceptBacklog=-1
+eventBusOptions.activityLogDataFormat=HEX_DUMP
+eventBusOptions.clientAuth=NONE
+eventBusOptions.clusterPingInterval=20000
+eventBusOptions.clusterPingReplyInterval=20000
+eventBusOptions.connectTimeout=60000
+eventBusOptions.enabledSecureTransportProtocols.0=TLSv1.2
+eventBusOptions.enabledSecureTransportProtocols.1=TLSv1.3
+eventBusOptions.idleTimeout=
+eventBusOptions.idleTimeoutUnit=SECONDS
+eventBusOptions.logActivity=
+eventBusOptions.port=
+eventBusOptions.readIdleTimeout=
+eventBusOptions.receiveBufferSize=-1
+eventBusOptions.reconnectAttempts=
+eventBusOptions.reconnectInterval=1000
+eventBusOptions.reuseAddress=true
+eventBusOptions.reusePort=
+eventBusOptions.sendBufferSize=-1
+eventBusOptions.soLinger=-1
+eventBusOptions.ssl=
+eventBusOptions.sslHandshakeTimeout=10
+eventBusOptions.sslHandshakeTimeoutUnit=SECONDS
+eventBusOptions.tcpCork=
+eventBusOptions.tcpFastOpen=
+eventBusOptions.tcpKeepAlive=
+eventBusOptions.tcpNoDelay=true
+eventBusOptions.tcpQuickAck=
+eventBusOptions.tcpUserTimeout=
+eventBusOptions.trafficClass=-1
+eventBusOptions.trustAll=true
+eventBusOptions.useAlpn=
+eventBusOptions.writeIdleTimeout=
+fileSystemOptions.classPathResolvingEnabled=true
+fileSystemOptions.fileCacheDir=/var/folders/sw/x62h2y5d4hl1kj1_3d7jzvzh0000gn/T//vertx-cache
+fileSystemOptions.fileCachingEnabled=true
+metricsOptions.enabled=true
+metricsOptions.jvmMetricsEnabled=true
+metricsOptions.labels.0=HTTP_ROUTE
+metricsOptions.labels.1=HTTP_METHOD
+metricsOptions.labels.2=HTTP_CODE
+metricsOptions.labels.3=EB_SIDE
+metricsOptions.labels.4=POOL_TYPE
+metricsOptions.metricsNaming.clientProcessingPending=processing.pending
+metricsOptions.metricsNaming.clientProcessingTime=processing.time
+metricsOptions.metricsNaming.clientQueuePending=queue.pending
+metricsOptions.metricsNaming.clientQueueTime=queue.time
+metricsOptions.metricsNaming.clientResetsCount=resets
+metricsOptions.metricsNaming.datagramBytesRead=bytes.read
+metricsOptions.metricsNaming.datagramBytesWritten=bytes.written
+metricsOptions.metricsNaming.datagramErrorCount=errors
+metricsOptions.metricsNaming.ebBytesRead=bytes.read
+metricsOptions.metricsNaming.ebBytesWritten=bytes.written
+metricsOptions.metricsNaming.ebDelivered=delivered
+metricsOptions.metricsNaming.ebDiscarded=discarded
+metricsOptions.metricsNaming.ebHandlers=handlers
+metricsOptions.metricsNaming.ebPending=pending
+metricsOptions.metricsNaming.ebProcessed=processed
+metricsOptions.metricsNaming.ebPublished=published
+metricsOptions.metricsNaming.ebReceived=received
+metricsOptions.metricsNaming.ebReplyFailures=reply.failures
+metricsOptions.metricsNaming.ebSent=sent
+metricsOptions.metricsNaming.httpActiveRequests=active.requests
+metricsOptions.metricsNaming.httpActiveWsConnections=active.ws.connections
+metricsOptions.metricsNaming.httpQueuePending=queue.pending
+metricsOptions.metricsNaming.httpQueueTime=queue.time
+metricsOptions.metricsNaming.httpRequestBytes=request.bytes
+metricsOptions.metricsNaming.httpRequestResetsCount=request.resets
+metricsOptions.metricsNaming.httpRequestsCount=requests
+metricsOptions.metricsNaming.httpResponseBytes=response.bytes
+metricsOptions.metricsNaming.httpResponseTime=response.time
+metricsOptions.metricsNaming.httpResponsesCount=responses
+metricsOptions.metricsNaming.netActiveConnections=active.connections
+metricsOptions.metricsNaming.netBytesRead=bytes.read
+metricsOptions.metricsNaming.netBytesWritten=bytes.written
+metricsOptions.metricsNaming.netErrorCount=errors
+metricsOptions.metricsNaming.poolCompleted=completed
+metricsOptions.metricsNaming.poolInUse=in.use
+metricsOptions.metricsNaming.poolQueuePending=queue.pending
+metricsOptions.metricsNaming.poolQueueTime=queue.time
+metricsOptions.metricsNaming.poolUsage=usage
+metricsOptions.metricsNaming.poolUsageRatio=ratio
+metricsOptions.prometheusOptions.embeddedServerEndpoint=/metrics
+metricsOptions.prometheusOptions.enabled=true
+metricsOptions.prometheusOptions.publishQuantiles=true
+metricsOptions.prometheusOptions.startEmbeddedServer=
+metricsOptions.registryName=default
+
diff --git a/config/traces-api/log4j2-dev.xml b/config/traces-api/log4j2-dev.xml
new file mode 100644
index 000000000..0bed13c21
--- /dev/null
+++ b/config/traces-api/log4j2-dev.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/config/traces-api/traces-app-docker.config.toml b/config/traces-api/traces-app-docker.config.toml
new file mode 100644
index 000000000..8d364b75c
--- /dev/null
+++ b/config/traces-api/traces-app-docker.config.toml
@@ -0,0 +1,17 @@
+input_traces_directory = "/data/traces/raw"
+output_traces_directory = "/data/traces/conflated"
+traces_version = "0.2.0"
+traces_file_extension = "json.gz"
+
+# Inmemory cache of the traces read from the file system
+# Save IO and CPU unzipping and JSON parsing
+[read_traces_cache]
+size = 20
+expiration_duration = "PT20M"
+
+[api]
+port = 8080
+path = "/"
+# if =0, it will create one verticle per core (or hyperthread if supported)
+number_of_verticles = 0
+observability_port = 8090
diff --git a/config/traces-api/traces-app-local-dev.config.overrides.toml b/config/traces-api/traces-app-local-dev.config.overrides.toml
new file mode 100644
index 000000000..2fb9bfce4
--- /dev/null
+++ b/config/traces-api/traces-app-local-dev.config.overrides.toml
@@ -0,0 +1,6 @@
+input_traces_directory = "tmp/local/traces/raw"
+output_traces_directory = "tmp/local/traces/conflated"
+traces_version = "0.2.0"
+
+[api]
+port = 8081
diff --git a/config/traces-api/vertx.properties b/config/traces-api/vertx.properties
new file mode 100644
index 000000000..d1843793c
--- /dev/null
+++ b/config/traces-api/vertx.properties
@@ -0,0 +1,21 @@
+# set -Dvertx.configurationFile=/path/to/vertx.properties
+# Vert.x configuration file
+
+# Event loop thread pool size
+# eventLoopPoolSize=10
+
+# Worker thread pool size
+# workerPoolSize=20
+
+# internalBlockingPoolSize=20
+
+# Event loop timeout (in milliseconds) before a blocked thread warning is issued
+warnEventLoopBlocked=5000
+
+# Maximum number of event loop execution iterations before a blocked warning is issued
+maxEventLoopExecuteTime=2
+maxEventLoopExecuteTimeUnit=MINUTES
+maxWorkerExecuteTime=2
+maxWorkerExecuteTimeUnit=MINUTES
+logStacktraceThreshold=500
+preferNativeTransport=true
diff --git a/consensus-layer-app/README.md b/consensus-layer-app/README.md
new file mode 100644
index 000000000..88d840556
--- /dev/null
+++ b/consensus-layer-app/README.md
@@ -0,0 +1,3 @@
+# Consensus Layer App
+This micro application will play the role of Consensus Client for execution clients in Linea network. Will poll the latest ForkChoiceUpdate from a given endpoint and update it's local Execution client
+
diff --git a/consensus-layer-app/app/build.gradle b/consensus-layer-app/app/build.gradle
new file mode 100644
index 000000000..42714b25e
--- /dev/null
+++ b/consensus-layer-app/app/build.gradle
@@ -0,0 +1,52 @@
+plugins {
+ id 'net.consensys.zkevm.kotlin-application-conventions'
+}
+
+dependencies {
+ implementation project(':jvm-libs:json-rpc')
+ implementation project(':jvm-libs:metrics:monitoring')
+ implementation project(':jvm-libs:future-extensions')
+ implementation project(':jvm-libs:fork-choice-state-provider')
+ implementation project(':jvm-libs:vertx-helper')
+ implementation project(':jvm-libs:jsonwebtoken')
+
+ implementation "tech.pegasys.teku.internal:time:${versions.teku}"
+ implementation "tech.pegasys.teku.internal:infrastructure-events:${versions.teku}"
+ implementation "com.github.ben-manes.caffeine:caffeine:${versions.caffeine}"
+ implementation "io.vertx:vertx-core:${versions.vertx}"
+ implementation "io.vertx:vertx-web:${versions.vertx}"
+ implementation "io.vertx:vertx-health-check:${versions.vertx}"
+ implementation "io.vertx:vertx-lang-kotlin:${versions.vertx}"
+ implementation "io.vertx:vertx-config:${versions.vertx}"
+ implementation "io.vertx:vertx-micrometer-metrics:${versions.vertx}"
+ implementation "info.picocli:picocli:${versions.picoli}"
+ implementation "com.sksamuel.hoplite:hoplite-core:${versions.hoplite}"
+ implementation "com.sksamuel.hoplite:hoplite-toml:${versions.hoplite}"
+ implementation "io.micrometer:micrometer-registry-prometheus:${versions.micrometer}"
+ implementation "com.fasterxml.jackson.core:jackson-annotations:${versions.jackson}"
+ implementation "com.fasterxml.jackson.core:jackson-databind:${versions.jackson}"
+ implementation "com.fasterxml.jackson.module:jackson-module-kotlin:${versions.jackson}"
+ api("io.netty:netty-transport-native-epoll:${versions.netty}:linux-x86_64") {
+ because "It enables native transport for Linux."
+ // Note that its version should match netty version used in Vertx
+ }
+ api("io.netty:netty-transport-native-kqueue:${versions.netty}:osx-x86_64") {
+ because "It enables native transport for Mac OSX."
+ // Note that its version should match netty version used in Vertx
+ }
+
+ testImplementation "io.vertx:vertx-junit5:${versions.vertx}"
+ testImplementation "io.rest-assured:rest-assured:${versions.test.restassured}"
+ testImplementation "io.rest-assured:json-schema-validator:${versions.test.restassured}"
+}
+
+application {
+ mainClass = 'net.consensys.linea.consensus.app.ConsensusLayerAppMain'
+}
+
+
+run {
+ workingDir = rootProject.projectDir
+ jvmArgs = ["-Dvertx.configurationFile=consensus-layer-app/config/vertx.properties", "-Dlog4j2.configurationFile=consensus-layer-app/config/log4j2-dev.xml"]
+ args = ["consensus-layer-app/config/consensus-layer-app.config.toml"]
+}
diff --git a/consensus-layer-app/app/src/main/kotlin/net/consensys/linea/consensus/ForkChoicePoller.kt b/consensus-layer-app/app/src/main/kotlin/net/consensys/linea/consensus/ForkChoicePoller.kt
new file mode 100644
index 000000000..3fb31f62b
--- /dev/null
+++ b/consensus-layer-app/app/src/main/kotlin/net/consensys/linea/consensus/ForkChoicePoller.kt
@@ -0,0 +1,68 @@
+package net.consensys.linea.consensus
+
+import io.vertx.core.Vertx
+import net.consensys.linea.forkchoicestate.ForkChoiceState
+import net.consensys.linea.forkchoicestate.ForkChoiceStateInfoV0
+import org.apache.logging.log4j.LogManager
+import org.apache.logging.log4j.Logger
+import tech.pegasys.teku.ethereum.executionclient.ExecutionEngineClient
+import tech.pegasys.teku.ethereum.executionclient.schema.ForkChoiceStateV1
+import tech.pegasys.teku.infrastructure.async.SafeFuture
+import java.time.Duration
+import java.util.Optional
+
+fun ForkChoiceState.asTekuForkChoiceStateV1(): ForkChoiceStateV1 =
+ ForkChoiceStateV1(this.headBlockHash, this.safeBlockHash, this.finalizedBlockHash)
+
+class ForkChoicePoller(
+ private val vertx: Vertx,
+ private val pollingInterval: Duration,
+ private val forkChoiceStateClient: ForkChoiceStateClient,
+ private val executionClient: ExecutionEngineClient,
+ private val allowRevert: Boolean = false
+) {
+ private val log: Logger = LogManager.getLogger()
+ private var timerId: Long? = null
+ private var lastForkChoiceState: ForkChoiceStateInfoV0 =
+ forkChoiceStateClient.getForkChoiceState().get()
+
+ private fun updateInternalForkChoiceState(newForkChoice: ForkChoiceStateInfoV0): Boolean {
+ if (newForkChoice == lastForkChoiceState) return false
+
+ val isRevert = newForkChoice.headBlockNumber <= lastForkChoiceState.headBlockNumber
+ if (isRevert) {
+ log.warn("Revert detected: currentState={} newState={}", lastForkChoiceState, newForkChoice)
+ if (!allowRevert) {
+ return false
+ }
+ }
+ log.info("ForkChoiceState updated {} --> {}", lastForkChoiceState, newForkChoice)
+ lastForkChoiceState = newForkChoice
+ return true
+ }
+
+ fun updateExecutionLayer(fcu: ForkChoiceStateInfoV0): SafeFuture {
+ return executionClient
+ .forkChoiceUpdatedV2(fcu.forkChoiceState.asTekuForkChoiceStateV1(), Optional.empty())
+ .whenException { th -> log.error("Execution client update failed: {}", th) }
+ .thenAccept { result -> log.info("Execution client update result: {}", result) }
+ .thenApply { null }
+ }
+
+ fun updateFlow() {
+ forkChoiceStateClient.getForkChoiceState().thenApply {
+ forkChoiceStateInfo: ForkChoiceStateInfoV0 ->
+ if (updateInternalForkChoiceState(forkChoiceStateInfo)) {
+ this.updateExecutionLayer(forkChoiceStateInfo)
+ }
+ }
+ }
+
+ fun startPoller() {
+ timerId = vertx.setPeriodic(pollingInterval.toMillis()) { _ -> updateFlow() }
+ }
+
+ fun stopPoller() {
+ timerId?.let { vertx.cancelTimer(it) }
+ }
+}
diff --git a/consensus-layer-app/app/src/main/kotlin/net/consensys/linea/consensus/ForkChoiceStateClient.kt b/consensus-layer-app/app/src/main/kotlin/net/consensys/linea/consensus/ForkChoiceStateClient.kt
new file mode 100644
index 000000000..57036e1a2
--- /dev/null
+++ b/consensus-layer-app/app/src/main/kotlin/net/consensys/linea/consensus/ForkChoiceStateClient.kt
@@ -0,0 +1,43 @@
+package net.consensys.linea.consensus
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
+import com.github.michaelbull.result.mapBoth
+import io.vertx.core.json.JsonObject
+import net.consensys.linea.async.toSafeFuture
+import net.consensys.linea.forkchoicestate.ForkChoiceStateInfoV0
+import net.consensys.linea.jsonrpc.BaseJsonRpcRequest
+import net.consensys.linea.jsonrpc.JsonRpcSuccessResponse
+import net.consensys.linea.jsonrpc.client.JsonRpcClient
+import tech.pegasys.teku.infrastructure.async.SafeFuture
+import java.util.concurrent.atomic.AtomicLong
+
+interface ForkChoiceStateClient {
+ fun getForkChoiceState(): SafeFuture
+}
+
+class ForkChoiceStateJsonRpcClient(
+ private val jsonRpcClient: JsonRpcClient,
+ private val objectMapper: ObjectMapper = jacksonObjectMapper()
+) : ForkChoiceStateClient {
+ private val idCounter = AtomicLong(0)
+ private val requestTemplate =
+ BaseJsonRpcRequest("2.0", 0, "linea_getForkChoiceState", emptyList())
+
+ override fun getForkChoiceState(): SafeFuture {
+ return jsonRpcClient
+ .makeRequest(requestTemplate.copy(id = idCounter.getAndIncrement()))
+ .toSafeFuture()
+ .thenCompose() { result ->
+ result.mapBoth(
+ { SafeFuture.completedFuture(mapResult(it)) },
+ { SafeFuture.failedFuture(it.error.asException()) }
+ )
+ }
+ }
+
+ private fun mapResult(response: JsonRpcSuccessResponse): ForkChoiceStateInfoV0 {
+ val result = response.result as JsonObject
+ return objectMapper.readValue(result.encode(), ForkChoiceStateInfoV0::class.java)
+ }
+}
diff --git a/consensus-layer-app/app/src/main/kotlin/net/consensys/linea/consensus/app/ConsensusLayerApp.kt b/consensus-layer-app/app/src/main/kotlin/net/consensys/linea/consensus/app/ConsensusLayerApp.kt
new file mode 100644
index 000000000..368684ad9
--- /dev/null
+++ b/consensus-layer-app/app/src/main/kotlin/net/consensys/linea/consensus/app/ConsensusLayerApp.kt
@@ -0,0 +1,120 @@
+package net.consensys.linea.consensus.app
+
+import io.micrometer.core.instrument.MeterRegistry
+import io.vertx.core.Future
+import io.vertx.core.Vertx
+import io.vertx.core.VertxOptions
+import io.vertx.micrometer.MicrometerMetricsOptions
+import io.vertx.micrometer.VertxPrometheusOptions
+import io.vertx.micrometer.backends.BackendRegistries
+import net.consensys.linea.consensus.ForkChoicePoller
+import net.consensys.linea.consensus.ForkChoiceStateClient
+import net.consensys.linea.consensus.ForkChoiceStateJsonRpcClient
+import net.consensys.linea.consensus.app.api.ObservabilityApi
+import net.consensys.linea.forkchoicestate.ForkChoiceStateController
+import net.consensys.linea.forkchoicestate.api.ForkChoiceStateApi
+import net.consensys.linea.jsonrpc.client.VertxHttpJsonRpcClientFactory
+import net.consensys.linea.jwt.loadJwtSecretFromFile
+import net.consensys.linea.vertx.ObservabilityServer
+import net.consensys.linea.vertx.loadVertxConfig
+import org.apache.logging.log4j.LogManager
+import tech.pegasys.teku.ethereum.executionclient.ExecutionEngineClient
+import tech.pegasys.teku.ethereum.executionclient.auth.JwtConfig
+import tech.pegasys.teku.ethereum.executionclient.web3j.Web3JClient
+import tech.pegasys.teku.ethereum.executionclient.web3j.Web3JExecutionEngineClient
+import tech.pegasys.teku.ethereum.executionclient.web3j.Web3jClientBuilder
+import tech.pegasys.teku.infrastructure.time.SystemTimeProvider
+import java.time.Duration
+import java.util.Optional
+
+class ConsensusLayerApp(config: ConsensusLayerAppConfig) {
+ private val log = LogManager.getLogger(this::class.java)
+ private val vertx: Vertx
+ private val observabilityApi: ObservabilityApi
+ private val meterRegistry: MeterRegistry
+ private val forkChoiceStateClient: ForkChoiceStateClient
+ private val forkChoiceStateApi: ForkChoiceStateApi?
+ private val forkChoicePoller: ForkChoicePoller
+ private val httpExecutionClient: Web3JClient =
+ Web3jClientBuilder()
+ .endpoint(config.executionClient.url.toString())
+ .timeout(Duration.ofSeconds(20))
+ .timeProvider(SystemTimeProvider())
+ .executionClientEventsPublisher { false }
+ .jwtConfigOpt(
+ Optional.of(JwtConfig(loadJwtSecretFromFile(config.executionClient.jwtSecretFile)))
+ )
+ .build()
+ private val executionClient: ExecutionEngineClient =
+ Web3JExecutionEngineClient(httpExecutionClient)
+
+ init {
+ log.debug("System properties: {}", System.getProperties())
+ val vertxConfigJson = loadVertxConfig(System.getProperty("vertx.configurationFile"))
+ log.info("Vertx custom configs: {}", vertxConfigJson)
+ val vertxConfig =
+ VertxOptions(vertxConfigJson)
+ .setMetricsOptions(
+ MicrometerMetricsOptions()
+ .setJvmMetricsEnabled(true)
+ .setPrometheusOptions(
+ VertxPrometheusOptions().setPublishQuantiles(true).setEnabled(true)
+ )
+ .setEnabled(true)
+ )
+ log.debug("Vertx full configs: {}", vertxConfig)
+ log.info("App configs: {}", config)
+ this.vertx = Vertx.vertx(vertxConfig)
+ this.meterRegistry = BackendRegistries.getDefaultNow()
+ this.observabilityApi =
+ ObservabilityApi(
+ ObservabilityServer.Config(
+ "linea-consensus-client",
+ config.api.observabilityPort.toInt()
+ ),
+ vertx
+ )
+ this.forkChoiceStateClient =
+ ForkChoiceStateJsonRpcClient(
+ VertxHttpJsonRpcClientFactory(vertx, meterRegistry)
+ .create(config.forkChoiceSource.url, 2)
+ )
+
+ this.forkChoiceStateApi =
+ config.api.forkChoiceProvider?.let {
+ ForkChoiceStateApi(
+ config.api.forkChoiceProvider,
+ vertx,
+ meterRegistry,
+ ForkChoiceStateController(forkChoiceStateClient.getForkChoiceState().get())
+ )
+ }
+ this.forkChoicePoller =
+ ForkChoicePoller(
+ vertx,
+ config.forkChoiceSource.pollingInterval,
+ forkChoiceStateClient,
+ executionClient,
+ false
+ )
+
+ // update client with latest update
+ this.forkChoicePoller
+ .updateExecutionLayer(this.forkChoiceStateClient.getForkChoiceState().get())
+ .get()
+ }
+
+ fun start(): Future<*> {
+ forkChoicePoller.startPoller()
+ return (forkChoiceStateApi?.start() ?: Future.succeededFuture(null))
+ .compose { observabilityApi.start() }
+ .onComplete { log.info("App successfully started") }
+ }
+
+ fun stop(): Future<*> {
+ log.info("Shooting down app..")
+ return (forkChoiceStateApi?.start() ?: Future.succeededFuture(null))
+ .compose { observabilityApi.stop() }
+ .onComplete { log.info("App successfully closed") }
+ }
+}
diff --git a/consensus-layer-app/app/src/main/kotlin/net/consensys/linea/consensus/app/ConsensusLayerAppCli.kt b/consensus-layer-app/app/src/main/kotlin/net/consensys/linea/consensus/app/ConsensusLayerAppCli.kt
new file mode 100644
index 000000000..0877da083
--- /dev/null
+++ b/consensus-layer-app/app/src/main/kotlin/net/consensys/linea/consensus/app/ConsensusLayerAppCli.kt
@@ -0,0 +1,114 @@
+package net.consensys.linea.consensus.app
+
+import com.sksamuel.hoplite.ConfigFailure
+import com.sksamuel.hoplite.ConfigLoaderBuilder
+import com.sksamuel.hoplite.addFileSource
+import com.sksamuel.hoplite.fp.Validated
+import org.apache.logging.log4j.LogManager
+import org.apache.logging.log4j.Logger
+import picocli.CommandLine.Command
+import picocli.CommandLine.Parameters
+import java.io.File
+import java.io.PrintWriter
+import java.nio.charset.Charset
+import java.util.concurrent.Callable
+
+@Command(
+ name = ConsensusLayerAppCli.COMMAND_NAME,
+ showDefaultValues = true,
+ abbreviateSynopsis = true,
+ description = ["Runs Linea Consensus Client for the Read Replicas"],
+ version = ["0.0.1"],
+ synopsisHeading = "%n",
+ descriptionHeading = "%nDescription:%n%n",
+ optionListHeading = "%nOptions:%n",
+ footerHeading = "%n"
+)
+class ConsensusLayerAppCli
+internal constructor(private val errorWriter: PrintWriter, private val startAction: StartAction) :
+ Callable {
+ @Parameters(paramLabel = "CONFIG.toml", description = ["Configuration files"])
+ private val configFiles: List? = null
+
+ override fun call(): Int {
+ return try {
+ if (configFiles == null) {
+ errorWriter.println("Please provide a configuration file!")
+ printUsage(errorWriter)
+ return 1
+ }
+ for (configFile in configFiles) {
+ if (!canReadFile(configFile)) {
+ return 1
+ }
+ }
+ val configs: Validated = configs(configFiles)
+ if (configs.isInvalid()) {
+ errorWriter.println(configs.getInvalidUnsafe().description())
+ return 1
+ }
+ startAction.start(configs.getUnsafe())
+ 0
+ } catch (e: Exception) {
+ reportUserError(e)
+ 1
+ }
+ }
+
+ private fun canReadFile(file: File): Boolean {
+ if (!file.canRead()) {
+ errorWriter.println("Cannot read configuration file '${file.absolutePath}'")
+ return false
+ }
+ return true
+ }
+
+ fun configs(configFiles: List): Validated {
+ val confBuilder: ConfigLoaderBuilder = ConfigLoaderBuilder.Companion.empty().addDefaults()
+ for (i in configFiles.indices.reversed()) {
+ // files must be added in reverse order for overriding
+
+ // files must be added in reverse order for overriding
+ confBuilder.addFileSource(configFiles[i], false)
+ }
+ val config: Validated =
+ confBuilder.build().loadConfig(emptyList())
+
+ return config
+ }
+
+ fun reportUserError(ex: Throwable) {
+ logger.fatal(ex.message, ex)
+ errorWriter.println(ex.message)
+ printUsage(errorWriter)
+ }
+
+ private fun printUsage(outputWriter: PrintWriter) {
+ outputWriter.println()
+ outputWriter.println("To display full help:")
+ outputWriter.println(COMMAND_NAME + " --help")
+ }
+
+ /**
+ * Not using a static field for this log instance because some code in this class executes prior
+ * to the logging configuration being applied so it's not always safe to use the logger.
+ *
+ * Where this is used we also ensure the messages are printed to the error writer so they will be
+ * printed even if logging is not yet configured.
+ *
+ * @return the logger for this class
+ */
+ private val logger: Logger = LogManager.getLogger()
+
+ fun interface StartAction {
+ fun start(configs: ConsensusLayerAppConfig)
+ }
+
+ companion object {
+ const val COMMAND_NAME = "traces"
+ fun withAction(startAction: StartAction): ConsensusLayerAppCli {
+ val errorWriter = PrintWriter(System.err, true, Charset.defaultCharset())
+ return ConsensusLayerAppCli(errorWriter, startAction)
+ }
+ }
+}
diff --git a/consensus-layer-app/app/src/main/kotlin/net/consensys/linea/consensus/app/ConsensusLayerAppConfig.kt b/consensus-layer-app/app/src/main/kotlin/net/consensys/linea/consensus/app/ConsensusLayerAppConfig.kt
new file mode 100644
index 000000000..e37d3b4f0
--- /dev/null
+++ b/consensus-layer-app/app/src/main/kotlin/net/consensys/linea/consensus/app/ConsensusLayerAppConfig.kt
@@ -0,0 +1,25 @@
+package net.consensys.linea.consensus.app
+
+import com.sksamuel.hoplite.ConfigAlias
+import net.consensys.linea.forkchoicestate.api.ForkChoiceStateApiConfig
+import java.net.URL
+import java.nio.file.Path
+import java.time.Duration
+
+data class ExecutionClientConfig(
+ val url: URL,
+ @ConfigAlias("jwt-secret-file") val jwtSecretFile: Path
+)
+
+data class ForkChoiceSource(val url: URL, val pollingInterval: Duration)
+
+data class ApiConfig(
+ val observabilityPort: UInt,
+ val forkChoiceProvider: ForkChoiceStateApiConfig?
+)
+
+data class ConsensusLayerAppConfig(
+ val forkChoiceSource: ForkChoiceSource,
+ val api: ApiConfig,
+ val executionClient: ExecutionClientConfig
+)
diff --git a/consensus-layer-app/app/src/main/kotlin/net/consensys/linea/consensus/app/ConsensusLayerAppMain.kt b/consensus-layer-app/app/src/main/kotlin/net/consensys/linea/consensus/app/ConsensusLayerAppMain.kt
new file mode 100644
index 000000000..ee81f93d1
--- /dev/null
+++ b/consensus-layer-app/app/src/main/kotlin/net/consensys/linea/consensus/app/ConsensusLayerAppMain.kt
@@ -0,0 +1,43 @@
+package net.consensys.linea.consensus.app
+
+import org.apache.logging.log4j.LogManager
+import org.apache.logging.log4j.core.LoggerContext
+import org.apache.logging.log4j.core.config.Configurator
+import picocli.CommandLine
+import kotlin.system.exitProcess
+
+class ConsensusLayerAppMain {
+ companion object {
+ private val log = LogManager.getLogger(ConsensusLayerAppMain::class)
+
+ @JvmStatic
+ fun main(args: Array) {
+ val cmd = CommandLine(ConsensusLayerAppCli.withAction(::startApp))
+ cmd.setExecutionExceptionHandler { ex, _, _ ->
+ log.error("Execution failure: ", ex)
+ 1
+ }
+ cmd.setParameterExceptionHandler { ex, _ ->
+ log.error("Invalid args!: ", ex)
+ 1
+ }
+ exitProcess(cmd.execute(*args))
+ }
+
+ private fun startApp(configs: ConsensusLayerAppConfig) {
+ val app = ConsensusLayerApp(configs)
+ Runtime.getRuntime()
+ .addShutdownHook(
+ Thread {
+ app.stop()
+ if (LogManager.getContext() is LoggerContext) {
+ // Disable log4j auto shutdown hook is not used otherwise
+ // Messages in App.stop won't appear in the logs
+ Configurator.shutdown(LogManager.getContext() as LoggerContext)
+ }
+ }
+ )
+ app.start()
+ }
+ }
+}
diff --git a/consensus-layer-app/app/src/main/kotlin/net/consensys/linea/consensus/app/api/ObservabilityApi.kt b/consensus-layer-app/app/src/main/kotlin/net/consensys/linea/consensus/app/api/ObservabilityApi.kt
new file mode 100644
index 000000000..e982fe857
--- /dev/null
+++ b/consensus-layer-app/app/src/main/kotlin/net/consensys/linea/consensus/app/api/ObservabilityApi.kt
@@ -0,0 +1,19 @@
+package net.consensys.linea.consensus.app.api
+
+import io.vertx.core.Future
+import io.vertx.core.Vertx
+import net.consensys.linea.vertx.ObservabilityServer
+
+class ObservabilityApi(private val config: ObservabilityServer.Config, private val vertx: Vertx) {
+ private var monitorServerId: String? = null
+
+ fun start(): Future<*> {
+ return vertx.deployVerticle(ObservabilityServer(config)).onSuccess { monitorVerticleId ->
+ this.monitorServerId = monitorVerticleId
+ }
+ }
+
+ fun stop(): Future<*> {
+ return this.monitorServerId?.let { vertx.undeploy(it) } ?: Future.succeededFuture(null)
+ }
+}
diff --git a/consensus-layer-app/config/consensus-layer-app.config.toml b/consensus-layer-app/config/consensus-layer-app.config.toml
new file mode 100644
index 000000000..d51f15627
--- /dev/null
+++ b/consensus-layer-app/config/consensus-layer-app.config.toml
@@ -0,0 +1,24 @@
+# Souce endpoint to poll for the latest forkchoice
+[fork-choice-source]
+url="http://127.0.0.1:8081/linea/api/consensus/v0"
+# formats accepted are based on the ISO-8601 duration format PnDTnHnMn.nS
+polling-interval="PT0.5S"
+
+[execution-client]
+# zkgeth traces node
+url = "http://127.0.0.1:8651"
+# besu-follower
+#url = "http://127.0.0.1:8850"
+jwt-secret-file="docker/config/jwt-secret.hex"
+
+[api]
+observability_port = 8090
+
+# This enables other peers to fetch the lattest forkchoice state from this node as well
+# Useful for Public Facing instances, where this can act as man in the middle.
+# Comment section bellow to disable this feature
+[api.fork-choice-provider]
+port = 8082
+path = "/linea/api/consensus/v0"
+# if =0, it will create one verticle per core (or hyperthread if supported)
+number_of_verticles = 1
diff --git a/consensus-layer-app/config/log4j2-dev.xml b/consensus-layer-app/config/log4j2-dev.xml
new file mode 100644
index 000000000..7f99c1519
--- /dev/null
+++ b/consensus-layer-app/config/log4j2-dev.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/consensus-layer-app/config/vertx.properties b/consensus-layer-app/config/vertx.properties
new file mode 100644
index 000000000..063ef547d
--- /dev/null
+++ b/consensus-layer-app/config/vertx.properties
@@ -0,0 +1,21 @@
+# set -Dvertx.configurationFile=/path/to/vertx.properties
+# Vert.x configuration file
+
+# Event loop thread pool size
+# eventLoopPoolSize=10
+
+# Worker thread pool size
+# workerPoolSize=20
+
+# internalBlockingPoolSize=20
+
+# Event loop timeout (in milliseconds) before a blocked thread warning is issued
+warnEventLoopBlocked=5000
+
+# Maximum number of event loop execution iterations before a blocked warning is issued
+maxEventLoopExecuteTime=5000
+maxEventLoopExecuteTimeUnit=MILLISECONDS
+maxWorkerExecuteTime=6000
+maxWorkerExecuteTimeUnit=MILLISECONDS
+logStacktraceThreshold=500
+preferNativeTransport=true
diff --git a/contracts/.env.template b/contracts/.env.template
new file mode 100644
index 000000000..8ea7473fb
--- /dev/null
+++ b/contracts/.env.template
@@ -0,0 +1,42 @@
+TIMELOCK_PROPOSERS="0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"
+TIMELOCK_EXECUTORS="0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"
+
+ZKEVMV2_SECURITY_COUNCIL="0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"
+ZKEVMV2_OPERATORS="0xd0584d4d37157f7105a4b41ed8ecbdfafdb2547f"
+ZKEVMV2_RATE_LIMIT_PERIOD="86400" #24Hours in seconds
+ZKEVMV2_RATE_LIMIT_AMOUNT="1000000000000000000000" #1000ETH
+
+L2MSGSERVICE_SECURITY_COUNCIL="0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"
+L2MSGSERVICE_L1L2_MESSAGE_SETTER="0x90F79bf6EB2c4f870365E785982E1f101E93b906"
+L2MSGSERVICE_RATE_LIMIT_PERIOD="86400" #24Hours in seconds
+L2MSGSERVICE_RATE_LIMIT_AMOUNT="1000000000000000000000" #1000ETH
+
+BLOCKCHAIN_NODE="https://goerli.infura.io/v3/"
+L2_BLOCKCHAIN_NODE="https://linea-goerli.infura.io/v3/"
+DEPLOYMOCKVERIFIER="FALSE"
+PRIVATE_KEY=
+
+TRANSACTION_DECODER_ADDRESS="0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512"
+VERIFIER_ADDRESS="0x5FbDB2315678afecb367f032d93F642f64180aa3"
+VERIFIER_PROOF="1"
+VERIFIER_CONTRACT_NAME="PlonkVerifier"
+
+ZKEVMV2_ADDRESS="0xC737F2334651ea85A72D8DA9d933c821A8377F9f"
+NEW_CONTRACT_NAME="ZkEvmV2"
+PROXY_ADDRESS="0xC737F2334651ea85A72D8DA9d933c821A8377F9f"
+ZKEVMV2_INITIAL_L2_BLOCK_NUMBER="0"
+ZKEVMV2_INITIAL_STATE_ROOT_HASH="0x113e9977cebf08f3b271d121342540fce95530c206c2a878ae957925e1d0fc02"
+
+WITHDRAW_LIMIT_IN_WEI=100000000000000000000000
+MESSAGE_SERVICE_ADDRESS="0xe537D669CA013d86EBeF1D64e40fC74CADC91987"
+MESSAGE_SERVICE_TYPE="L2MessageService"
+
+# Token Bridge
+L1_RESERVED_TOKEN_ADDRESSES=
+L2_RESERVED_TOKEN_ADDRESSES=
+
+LINEA_SAFE_L1=
+LINEA_SAFE_L2=
+
+ETHERSCAN_API_KEY=
+LINEASCAN_API_KEY=
diff --git a/contracts/.env.template.ci b/contracts/.env.template.ci
new file mode 100644
index 000000000..f7c72deae
--- /dev/null
+++ b/contracts/.env.template.ci
@@ -0,0 +1,32 @@
+TIMELOCK_PROPOSERS="0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"
+TIMELOCK_EXECUTORS="0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"
+
+ZKEVMV2_SECURITY_COUNCIL="0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"
+ZKEVMV2_OPERATORS="0xd0584d4d37157f7105a4b41ed8ecbdfafdb2547f"
+ZKEVMV2_RATE_LIMIT_PERIOD="86400" #24Hours in seconds
+ZKEVMV2_RATE_LIMIT_AMOUNT="1000000000000000000000" #1000ETH
+
+L2MSGSERVICE_SECURITY_COUNCIL="0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"
+L2MSGSERVICE_L1L2_MESSAGE_SETTER="0x90F79bf6EB2c4f870365E785982E1f101E93b906"
+L2MSGSERVICE_RATE_LIMIT_PERIOD="86400" #24Hours in seconds
+L2MSGSERVICE_RATE_LIMIT_AMOUNT="1000000000000000000000" #1000ETH
+
+BLOCKCHAIN_NODE="https://goerli.infura.io/v3/"
+L2_BLOCKCHAIN_NODE="https://linea-goerli.infura.io/v3/"
+DEPLOYMOCKVERIFIER="FALSE"
+PRIVATE_KEY=0xae99052fbea8f9c092f6d7c5132d585edc81aa1f11e4b1d18ac8fce0db44a078
+
+TRANSACTION_DECODER_ADDRESS="0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512"
+VERIFIER_ADDRESS="0x5FbDB2315678afecb367f032d93F642f64180aa3"
+VERIFIER_PROOF="1"
+VERIFIER_CONTRACT_NAME="PlonkVerifier"
+
+ZKEVMV2_ADDRESS="0xC737F2334651ea85A72D8DA9d933c821A8377F9f"
+NEW_CONTRACT_NAME="ZkEvmV2"
+PROXY_ADDRESS="0xC737F2334651ea85A72D8DA9d933c821A8377F9f"
+ZKEVMV2_INITIAL_L2_BLOCK_NUMBER="0"
+ZKEVMV2_INITIAL_STATE_ROOT_HASH="0x113e9977cebf08f3b271d121342540fce95530c206c2a878ae957925e1d0fc02"
+
+WITHDRAW_LIMIT_IN_WEI=100000000000000000000000
+MESSAGE_SERVICE_ADDRESS="0xe537D669CA013d86EBeF1D64e40fC74CADC91987"
+MESSAGE_SERVICE_TYPE="L2MessageService"
diff --git a/contracts/.env.template.uat b/contracts/.env.template.uat
new file mode 100644
index 000000000..7d19bd924
--- /dev/null
+++ b/contracts/.env.template.uat
@@ -0,0 +1,40 @@
+TIMELOCK_PROPOSERS="0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"
+TIMELOCK_EXECUTORS="0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"
+
+ZKEVMV2_SECURITY_COUNCIL="0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"
+ZKEVMV2_OPERATORS="0xd0584d4d37157f7105a4b41ed8ecbdfafdb2547f"
+ZKEVMV2_RATE_LIMIT_PERIOD="86400" #24Hours in seconds
+ZKEVMV2_RATE_LIMIT_AMOUNT="1000000000000000000000" #1000ETH
+
+L2MSGSERVICE_SECURITY_COUNCIL="0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"
+L2MSGSERVICE_L1L2_MESSAGE_SETTER="0x90F79bf6EB2c4f870365E785982E1f101E93b906"
+L2MSGSERVICE_RATE_LIMIT_PERIOD="86400" #24Hours in seconds
+L2MSGSERVICE_RATE_LIMIT_AMOUNT="1000000000000000000000" #1000ETH
+
+BLOCKCHAIN_NODE="https://goerli.infura.io/v3/"
+L2_BLOCKCHAIN_NODE=="https://linea-goerli.infura.io/v3/"
+DEPLOYMOCKVERIFIER="FALSE"
+PRIVATE_KEY=
+
+TRANSACTION_DECODER_ADDRESS="0x064016694d55eA267c1fA1C4354Bedc7e7eB0F59"
+VERIFIER_ADDRESS="0x5FbDB2315678afecb367f032d93F642f64180aa3"
+VERIFIER_PROOF="1"
+VERIFIER_CONTRACT_NAME="PlonkVerifier"
+
+ZKEVMV2_ADDRESS="0x9D446A58DbFFb7034aa50c1b75ac055dC569949E"
+NEW_CONTRACT_NAME="ZkEvmV2Init"
+OLD_CONTRACT_NAME="ZkEvmV2"
+PROXY_ADDRESS="0x9D446A58DbFFb7034aa50c1b75ac055dC569949E"
+ZKEVMV2_INITIAL_L2_BLOCK_NUMBER="899999"
+ZKEVMV2_INITIAL_STATE_ROOT_HASH="0xeedc9df2056591c1a4e452ab5a09ebfc90a626317d058ff5ddfec35a27fa7123"
+
+WITHDRAW_LIMIT_IN_WEI=100000000000000000000000
+MESSAGE_SERVICE_ADDRESS="0x6d925938Edb8A16B3035A4cF34FAA090f490202a"
+MESSAGE_SERVICE_TYPE="L2MessageService"
+
+# Token Bridge
+L1_RESERVED_TOKEN_ADDRESSES=0x07865c6E87B9F70255377e024ace6630C1Eaa37F
+L2_RESERVED_TOKEN_ADDRESSES=0xf56dc6695cF1f5c364eDEbC7Dc7077ac9B586068
+
+ETHERSCAN_API_KEY=
+LINEASCAN_API_KEY=
\ No newline at end of file
diff --git a/contracts/.eslintignore b/contracts/.eslintignore
new file mode 100644
index 000000000..75e07224b
--- /dev/null
+++ b/contracts/.eslintignore
@@ -0,0 +1,5 @@
+node_modules
+build
+cache
+coverage
+typechain-types
\ No newline at end of file
diff --git a/contracts/.eslintrc.js b/contracts/.eslintrc.js
new file mode 100644
index 000000000..887df1bf7
--- /dev/null
+++ b/contracts/.eslintrc.js
@@ -0,0 +1,14 @@
+module.exports = {
+ env: {
+ browser: false,
+ es2021: true,
+ mocha: true,
+ node: true,
+ },
+ plugins: ["@typescript-eslint"],
+ extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"],
+ parser: "@typescript-eslint/parser",
+ parserOptions: {
+ ecmaVersion: 12,
+ },
+};
diff --git a/contracts/.exclude_copy_list b/contracts/.exclude_copy_list
new file mode 100644
index 000000000..7d590db60
--- /dev/null
+++ b/contracts/.exclude_copy_list
@@ -0,0 +1,17 @@
+node_modules
+.env*
+coverage
+coverage.json
+typechain
+typechain-types
+cache
+build
+.openzeppelin
+abi
+Makefile
+README.md
+scripts/operational
+test/V2 Scenarios
+contracts/test-contracts/IntegrationTestTrueVerifier.sol
+mirror_linea_contracts*.sh
+.exclude_copy_list
diff --git a/contracts/.gitignore b/contracts/.gitignore
new file mode 100644
index 000000000..dc1257a51
--- /dev/null
+++ b/contracts/.gitignore
@@ -0,0 +1,10 @@
+node_modules
+.env
+coverage
+coverage.json
+typechain
+typechain-types
+cache
+build
+.openzeppelin
+
diff --git a/contracts/.prettierignore b/contracts/.prettierignore
new file mode 100644
index 000000000..035570746
--- /dev/null
+++ b/contracts/.prettierignore
@@ -0,0 +1,6 @@
+node_modules
+build
+cache
+coverage*
+gasReporterOutput.json
+typechain-types
diff --git a/contracts/.prettierrc.json b/contracts/.prettierrc.json
new file mode 100644
index 000000000..d2a7b90fc
--- /dev/null
+++ b/contracts/.prettierrc.json
@@ -0,0 +1,8 @@
+{
+ "trailingComma": "all",
+ "tabWidth": 2,
+ "semi": true,
+ "singleQuote": false,
+ "printWidth": 120,
+ "bracketSpacing": true
+}
\ No newline at end of file
diff --git a/contracts/.solcover.js b/contracts/.solcover.js
new file mode 100644
index 000000000..6c8d4641b
--- /dev/null
+++ b/contracts/.solcover.js
@@ -0,0 +1,3 @@
+module.exports = {
+ skipFiles: ["test-contracts", "verifiers/test"],
+};
diff --git a/contracts/.solhint.json b/contracts/.solhint.json
new file mode 100644
index 000000000..94b7cb7bd
--- /dev/null
+++ b/contracts/.solhint.json
@@ -0,0 +1,10 @@
+{
+ "extends": "solhint:recommended",
+ "rules": {
+ "prettier/prettier": "error",
+ "compiler-version": ["error", "^0.8.0"],
+ "func-visibility": ["warn", { "ignoreConstructors": true }],
+ "no-inline-assembly": "off"
+ },
+ "plugins": ["prettier"]
+}
\ No newline at end of file
diff --git a/contracts/Makefile b/contracts/Makefile
new file mode 100644
index 000000000..9be466b02
--- /dev/null
+++ b/contracts/Makefile
@@ -0,0 +1,117 @@
+.PHONY: test coverage
+
+compile:
+ npm run build
+ npx hardhat run scripts/hardhat/postCompile.ts
+
+clean:
+ rm -rf data/
+ rm -rf coverage/
+ rm -rf build/
+ rm -rf cache/
+ mkdir -p data
+ rm -f contracts/verifiers/*.sol
+
+lint:
+ npm run solhint:check
+
+test: export SKIP_DEPLOY_LOG := true
+test:
+ npm test
+
+fmt:
+ npm run lint:check
+
+fmt-fix:
+ npm run lint:fix
+
+coverage: export SKIP_DEPLOY_LOG := true
+coverage:
+ npm run coverage
+
+deploy:
+ npx hardhat run --network besu scripts/hardhat/deploy.ts
+
+deploy-local: export BLOCKCHAIN_NODE := http://127.0.0.1:8545
+deploy-local: export ROLLUP_JSON_PATH := $(shell pwd)/data/rollup.json
+deploy-local: export SMC_CONFIG_PATH := $(shell pwd)/data/smc_config.json
+deploy-local: export ERC20_ADDR_PATH := $(shell pwd)/data/erc20_addr.addr
+deploy-local: deploy
+
+deploy-zkevm:
+ npx hardhat run --network zkevm_dev scripts/hardhat/deploy.ts
+
+deploy-local-zkevm: export BLOCKCHAIN_NODE := http://127.0.0.1:8445
+deploy-local-zkevm: export ROLLUP_JSON_PATH := $(shell pwd)/data/rollup.json
+deploy-local-zkevm: export SMC_CONFIG_PATH := $(shell pwd)/data/smc_config.json
+deploy-local-zkevm: export TX_GAS_LIMIT := 10000000
+deploy-local-zkevm: export BLOCKCHAIN_GAS_PRICE := 1400000000
+deploy-local-zkevm: export BLOCKCHAIN_TIMEOUT_MS := 30000
+deploy-local-zkevm: deploy-zkevm
+
+register-operator-in-smc-local: export BLOCKCHAIN_NODE := http://127.0.0.1:8545
+register-operator-in-smc-local: export SMC_CONFIG_PATH := $(shell pwd)/data/smc_config.json
+register-operator-in-smc-local:
+ ts-node scripts/registerOperator.ts data/rollup.json ../node-data/test/keys/contract_owner.acc ../node-data/test/keys/operator_1.acc
+
+register-operator-2-in-smc-local: export BLOCKCHAIN_NODE := http://127.0.0.1:8545
+register-operator-2-in-smc-local: export SMC_CONFIG_PATH := $(shell pwd)/data/smc_config.json
+register-operator-2-in-smc-local:
+ ts-node scripts/registerOperator.ts data/rollup.json ../node-data/test/keys/contract_owner.acc ../node-data/test/keys/operator_2.acc
+
+register-erc20-local: export BLOCKCHAIN_NODE := http://127.0.0.1:8545
+register-erc20-local: export ROLLUP_JSON_PATH := $(shell pwd)/data/rollup.json
+register-erc20-local: export ERC_20_MAPPING_PATH := $(shell pwd)/data/erc_20_mapping.json
+register-erc20-local: export ERC20_ADDR_PATH := $(shell pwd)/data/erc20_addr.addr
+register-erc20-local: export ERC_20_NUMBER := 2
+register-erc20-local:
+ npx hardhat run --network besu scripts/hardhat/deployAndRegisterErc20.ts
+
+load-funds-to-smc: export BLOCKCHAIN_NODE := http://127.0.0.1:8545
+load-funds-to-smc:
+ ts-node scripts/sendEthToRollup.ts data/rollup.json ../node-data/test/keys/contract_owner.acc "90.10"
+
+print-all-test-balances:
+ npm run balance -- \
+ ../node-data/test/keys/contract_owner.acc \
+ ../node-data/test/keys/operator_1.acc \
+ ../node-data/test/keys/operator_2.acc \
+ ../node-data/test/keys/eth_account_3.acc \
+ ../node-data/test/keys/eth_account_4.acc \
+ ../node-data/test/keys/eth_account_5.acc \
+ ../node-data/test/keys/eth_account_6.acc \
+ ../node-data/test/keys/eth_account_7.acc \
+ ../node-data/test/keys/eth_account_8.acc \
+ ../node-data/test/keys/eth_account_9.acc \
+ ../node-data/test/keys/eth_account_10.acc \
+ data/rollup.json
+
+enable-zk-proof-verification:
+ ts-node scripts/changeZkVerification.ts data/rollup.json ../node-data/test/keys/contract_owner.acc 1
+
+disable-zk-proof-verification:
+ ts-node scripts/changeZkVerification.ts data/rollup.json ../node-data/test/keys/contract_owner.acc 0
+
+enable-signature-verification:
+ ts-node scripts/changeSignatureVerificaiton.ts data/rollup.json ../node-data/test/keys/contract_owner.acc 1
+
+print-zk-verification-status:
+ ts-node scripts/printZkVerification.ts data/rollup.json ../node-data/test/keys/contract_owner.acc
+
+listen-to-events: export DISABLE_BLOCKCHAIN_LOG := true
+listen-to-events:
+ ts-node scripts/printEvents.ts data/rollup.json ../node-data/test/keys/contract_owner.acc
+
+print-nonces: export DISABLE_BLOCKCHAIN_LOG := true
+print-nonces:
+ ts-node scripts/printNonces.ts ../node-data/test/keys/operator_1.acc
+
+run-ganache:
+ ganache-cli --allowUnlimitedContractSize --callGasLimit="0x1fffffffffffff" -l="0x1fffffffffffff" -g="0x0" --verbose --account=0xb17202c37cce9498e6f7dcdc1abd207802d09b5eee96677ea219ac867a198b91,90021000000000000000000000 --account=0x202454d1b4e72c41ebf58150030f649648d3cf5590297fb6718e27039ed9c86d,9000000000000000000000000
+
+.PHONY: deploy-testnet-token-bridge
+deploy-testnet-token-bridge:
+ npx hardhat run --network zkevm_dev scripts/tokenBridge/deploy-1.ts
+ npx hardhat run --network l2 scripts/tokenBridge/deploy-1.ts
+ npx hardhat run --network zkevm_dev scripts/tokenBridge/deploy-2.ts
+ npx hardhat run --network l2 scripts/tokenBridge/deploy-2.ts
\ No newline at end of file
diff --git a/contracts/README.md b/contracts/README.md
new file mode 100644
index 000000000..d516d6a49
--- /dev/null
+++ b/contracts/README.md
@@ -0,0 +1,121 @@
+# Smart Contract
+
+Contains Ethereum smart contract code for ConsenSys Rollups.
+
+# Development & Testing
+
+This project uses following libraries
+
+- [Ethers](https://github.com/ethers-io/ethers.js/) as Ethereum library
+- [Hardhat](https://hardhat.org/getting-started/) as development environment
+- [Chai](https://www.chaijs.com/) for assertions
+
+To run the tests:
+
+```bash
+make test
+```
+
+# Contract Deployment for Dev and Production
+
+Truffle is still used to deploy contracts to prod/dev blockchain and generate "data/".
+
+```bash
+make deploy-local
+```
+
+# Useful scripts
+
+Most of the scripts need to know address of Ethereum RPC. This is controlled via `BLOCKCHAIN_NODE` environment variable,
+
+for example run this before all the scripts in the same terminal:
+
+```bash
+export BLOCKCHAIN_NODE=http://localhost:5000
+```
+
+## Print balances of all test addresses:
+
+```bash
+make print-all-test-balances
+```
+
+## Register operator in smart contract
+
+```bash
+ts-node scripts/registerOperator.ts data/rollup.json ../node-data/test/keys/contract_owner.acc ../node-data/test/keys/operator_1.acc
+```
+
+or
+
+```bash
+make register-operator-in-smc-local
+```
+
+## Send inbound transfer to smart contract
+
+in ETH:
+
+Params are:
+please note, that amount is in ETH, not in wei.
+
+```bash
+export BLOCKCHAIN_NODE="http://localhost:8545"
+ts-node scripts/registerInboundEth.ts \
+ data/rollup.json \
+ ../node-data/test/keys/eth_account_3.acc \
+ 0 \
+ 100.3333
+```
+
+ERC 20
+Params are:
+
+amount is in the smallest units
+
+```bash
+export BLOCKCHAIN_NODE="http://localhost:8545"
+ts-node scripts/registerInboundErc20.ts \
+data/rollup.json \
+../node-data/test/keys/eth_account_3.acc \
+0 \
+0 \
+10000000
+```
+
+## Balance of ERC 20 token
+
+```
+export BLOCKCHAIN_NODE="http://localhost:8545"
+ts-node scripts/balanceOf.ts \
+data/rollup.json \
+../node-data/test/keys/eth_account_3.acc \
+1 \
+../node-data/test/keys/eth_account_3.acc \
+../node-data/test/keys/eth_account_4.acc \
+../node-data/test/keys/eth_account_5.acc
+```
+
+### To deploy ZkEvm to local docker compose
+
+Run in ./contracts with running docker-compose stack.
+
+```shell
+sed "s/BLOCKCHAIN_NODE=.*/BLOCKCHAIN_NODE=http:\/\/localhost:8445/" .env.template > .env
+npx hardhat run ./scripts/deployment/deployZkEVM.ts --network zkevm_dev
+```
+
+### To deploy ZkEvm to local docker compose
+
+Run in ./contracts with running docker-compose stack.
+
+```shell
+sed "s/BLOCKCHAIN_NODE=.*/BLOCKCHAIN_NODE=http:\/\/localhost:8445/" .env.template > .env
+npx hardhat run ./scripts/deployment/deployZkEVM.ts --network zkevm_dev
+```
+
+## Linea Token Bridge
+
+Token Bridge is a canonical brige between Ethereum and Linea networks.
+
+Documentation: [./docs/linea-token-bridge.md](./docs/linea-token-bridge.md)
diff --git a/contracts/abi/L2MessageService.abi b/contracts/abi/L2MessageService.abi
new file mode 100644
index 000000000..74a15f151
--- /dev/null
+++ b/contracts/abi/L2MessageService.abi
@@ -0,0 +1,923 @@
+[
+ {
+ "inputs": [],
+ "stateMutability": "nonpayable",
+ "type": "constructor"
+ },
+ {
+ "inputs": [],
+ "name": "EmptyMessageHashesArray",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "recipient",
+ "type": "address"
+ }
+ ],
+ "name": "FeePaymentFailed",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "FeeTooLow",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes32",
+ "name": "pauseType",
+ "type": "bytes32"
+ }
+ ],
+ "name": "IsNotPaused",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes32",
+ "name": "pauseType",
+ "type": "bytes32"
+ }
+ ],
+ "name": "IsPaused",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "LimitIsZero",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "MessageDoesNotExistOrHasAlreadyBeenClaimed",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "length",
+ "type": "uint256"
+ }
+ ],
+ "name": "MessageHashesListLengthHigherThanOneHundred",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "destination",
+ "type": "address"
+ }
+ ],
+ "name": "MessageSendingFailed",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "PeriodIsZero",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "RateLimitExceeded",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "ValueSentTooLow",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "ValueShouldBeGreaterThanFee",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "ZeroAddressNotAllowed",
+ "type": "error"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "resettingAddress",
+ "type": "address"
+ }
+ ],
+ "name": "AmountUsedInPeriodReset",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "uint8",
+ "name": "version",
+ "type": "uint8"
+ }
+ ],
+ "name": "Initialized",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "bytes32[]",
+ "name": "messageHashes",
+ "type": "bytes32[]"
+ }
+ ],
+ "name": "L1L2MessageHashesAddedToInbox",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "amountChangeBy",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "bool",
+ "name": "amountUsedLoweredToLimit",
+ "type": "bool"
+ },
+ {
+ "indexed": false,
+ "internalType": "bool",
+ "name": "usedAmountResetToZero",
+ "type": "bool"
+ }
+ ],
+ "name": "LimitAmountChanged",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "bytes32",
+ "name": "_messageHash",
+ "type": "bytes32"
+ }
+ ],
+ "name": "MessageClaimed",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "_from",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "_to",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "_fee",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "_value",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "_nonce",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "bytes",
+ "name": "_calldata",
+ "type": "bytes"
+ },
+ {
+ "indexed": true,
+ "internalType": "bytes32",
+ "name": "_messageHash",
+ "type": "bytes32"
+ }
+ ],
+ "name": "MessageSent",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "messageSender",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "bytes32",
+ "name": "pauseType",
+ "type": "bytes32"
+ }
+ ],
+ "name": "Paused",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "bytes32",
+ "name": "role",
+ "type": "bytes32"
+ },
+ {
+ "indexed": true,
+ "internalType": "bytes32",
+ "name": "previousAdminRole",
+ "type": "bytes32"
+ },
+ {
+ "indexed": true,
+ "internalType": "bytes32",
+ "name": "newAdminRole",
+ "type": "bytes32"
+ }
+ ],
+ "name": "RoleAdminChanged",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "bytes32",
+ "name": "role",
+ "type": "bytes32"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "account",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "sender",
+ "type": "address"
+ }
+ ],
+ "name": "RoleGranted",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "bytes32",
+ "name": "role",
+ "type": "bytes32"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "account",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "sender",
+ "type": "address"
+ }
+ ],
+ "name": "RoleRevoked",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "messageSender",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "bytes32",
+ "name": "pauseType",
+ "type": "bytes32"
+ }
+ ],
+ "name": "UnPaused",
+ "type": "event"
+ },
+ {
+ "inputs": [],
+ "name": "DEFAULT_ADMIN_ROLE",
+ "outputs": [
+ {
+ "internalType": "bytes32",
+ "name": "",
+ "type": "bytes32"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "GENERAL_PAUSE_TYPE",
+ "outputs": [
+ {
+ "internalType": "bytes32",
+ "name": "",
+ "type": "bytes32"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "INBOX_STATUS_CLAIMED",
+ "outputs": [
+ {
+ "internalType": "uint8",
+ "name": "",
+ "type": "uint8"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "INBOX_STATUS_RECEIVED",
+ "outputs": [
+ {
+ "internalType": "uint8",
+ "name": "",
+ "type": "uint8"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "INBOX_STATUS_UNKNOWN",
+ "outputs": [
+ {
+ "internalType": "uint8",
+ "name": "",
+ "type": "uint8"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "L1_L2_MESSAGE_SETTER_ROLE",
+ "outputs": [
+ {
+ "internalType": "bytes32",
+ "name": "",
+ "type": "bytes32"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "L1_L2_PAUSE_TYPE",
+ "outputs": [
+ {
+ "internalType": "bytes32",
+ "name": "",
+ "type": "bytes32"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "L2_L1_PAUSE_TYPE",
+ "outputs": [
+ {
+ "internalType": "bytes32",
+ "name": "",
+ "type": "bytes32"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "MINIMUM_FEE_SETTER_ROLE",
+ "outputs": [
+ {
+ "internalType": "bytes32",
+ "name": "",
+ "type": "bytes32"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "PAUSE_MANAGER_ROLE",
+ "outputs": [
+ {
+ "internalType": "bytes32",
+ "name": "",
+ "type": "bytes32"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "PROVING_SYSTEM_PAUSE_TYPE",
+ "outputs": [
+ {
+ "internalType": "bytes32",
+ "name": "",
+ "type": "bytes32"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "RATE_LIMIT_SETTER_ROLE",
+ "outputs": [
+ {
+ "internalType": "bytes32",
+ "name": "",
+ "type": "bytes32"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes32[]",
+ "name": "_messageHashes",
+ "type": "bytes32[]"
+ }
+ ],
+ "name": "addL1L2MessageHashes",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "_from",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "_to",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "_fee",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "_value",
+ "type": "uint256"
+ },
+ {
+ "internalType": "address payable",
+ "name": "_feeRecipient",
+ "type": "address"
+ },
+ {
+ "internalType": "bytes",
+ "name": "_calldata",
+ "type": "bytes"
+ },
+ {
+ "internalType": "uint256",
+ "name": "_nonce",
+ "type": "uint256"
+ }
+ ],
+ "name": "claimMessage",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "currentPeriodAmountInWei",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "currentPeriodEnd",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes32",
+ "name": "role",
+ "type": "bytes32"
+ }
+ ],
+ "name": "getRoleAdmin",
+ "outputs": [
+ {
+ "internalType": "bytes32",
+ "name": "",
+ "type": "bytes32"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes32",
+ "name": "role",
+ "type": "bytes32"
+ },
+ {
+ "internalType": "address",
+ "name": "account",
+ "type": "address"
+ }
+ ],
+ "name": "grantRole",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes32",
+ "name": "role",
+ "type": "bytes32"
+ },
+ {
+ "internalType": "address",
+ "name": "account",
+ "type": "address"
+ }
+ ],
+ "name": "hasRole",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes32",
+ "name": "",
+ "type": "bytes32"
+ }
+ ],
+ "name": "inboxL1L2MessageStatus",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "_securityCouncil",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "_l1l2MessageSetter",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "_rateLimitPeriod",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "_rateLimitAmount",
+ "type": "uint256"
+ }
+ ],
+ "name": "initialize",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "limitInWei",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "minimumFeeInWei",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "nextMessageNumber",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes32",
+ "name": "_pauseType",
+ "type": "bytes32"
+ }
+ ],
+ "name": "pauseByType",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes32",
+ "name": "",
+ "type": "bytes32"
+ }
+ ],
+ "name": "pauseTypeStatuses",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "periodInSeconds",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes32",
+ "name": "role",
+ "type": "bytes32"
+ },
+ {
+ "internalType": "address",
+ "name": "account",
+ "type": "address"
+ }
+ ],
+ "name": "renounceRole",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "resetAmountUsedInPeriod",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "_amount",
+ "type": "uint256"
+ }
+ ],
+ "name": "resetRateLimitAmount",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes32",
+ "name": "role",
+ "type": "bytes32"
+ },
+ {
+ "internalType": "address",
+ "name": "account",
+ "type": "address"
+ }
+ ],
+ "name": "revokeRole",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "_to",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "_fee",
+ "type": "uint256"
+ },
+ {
+ "internalType": "bytes",
+ "name": "_calldata",
+ "type": "bytes"
+ }
+ ],
+ "name": "sendMessage",
+ "outputs": [],
+ "stateMutability": "payable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "sender",
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "_feeInWei",
+ "type": "uint256"
+ }
+ ],
+ "name": "setMinimumFee",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes4",
+ "name": "interfaceId",
+ "type": "bytes4"
+ }
+ ],
+ "name": "supportsInterface",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes32",
+ "name": "_pauseType",
+ "type": "bytes32"
+ }
+ ],
+ "name": "unPauseByType",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "stateMutability": "payable",
+ "type": "receive"
+ }
+]
\ No newline at end of file
diff --git a/contracts/abi/TimeLock.abi b/contracts/abi/TimeLock.abi
new file mode 100644
index 000000000..e5bf0adfc
--- /dev/null
+++ b/contracts/abi/TimeLock.abi
@@ -0,0 +1,891 @@
+[
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "minDelay",
+ "type": "uint256"
+ },
+ {
+ "internalType": "address[]",
+ "name": "proposers",
+ "type": "address[]"
+ },
+ {
+ "internalType": "address[]",
+ "name": "executors",
+ "type": "address[]"
+ },
+ {
+ "internalType": "address",
+ "name": "admin",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "nonpayable",
+ "type": "constructor"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "bytes32",
+ "name": "id",
+ "type": "bytes32"
+ },
+ {
+ "indexed": true,
+ "internalType": "uint256",
+ "name": "index",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "target",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "value",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "bytes",
+ "name": "data",
+ "type": "bytes"
+ }
+ ],
+ "name": "CallExecuted",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "bytes32",
+ "name": "id",
+ "type": "bytes32"
+ },
+ {
+ "indexed": false,
+ "internalType": "bytes32",
+ "name": "salt",
+ "type": "bytes32"
+ }
+ ],
+ "name": "CallSalt",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "bytes32",
+ "name": "id",
+ "type": "bytes32"
+ },
+ {
+ "indexed": true,
+ "internalType": "uint256",
+ "name": "index",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "target",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "value",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "bytes",
+ "name": "data",
+ "type": "bytes"
+ },
+ {
+ "indexed": false,
+ "internalType": "bytes32",
+ "name": "predecessor",
+ "type": "bytes32"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "delay",
+ "type": "uint256"
+ }
+ ],
+ "name": "CallScheduled",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "bytes32",
+ "name": "id",
+ "type": "bytes32"
+ }
+ ],
+ "name": "Cancelled",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "oldDuration",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "newDuration",
+ "type": "uint256"
+ }
+ ],
+ "name": "MinDelayChange",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "bytes32",
+ "name": "role",
+ "type": "bytes32"
+ },
+ {
+ "indexed": true,
+ "internalType": "bytes32",
+ "name": "previousAdminRole",
+ "type": "bytes32"
+ },
+ {
+ "indexed": true,
+ "internalType": "bytes32",
+ "name": "newAdminRole",
+ "type": "bytes32"
+ }
+ ],
+ "name": "RoleAdminChanged",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "bytes32",
+ "name": "role",
+ "type": "bytes32"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "account",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "sender",
+ "type": "address"
+ }
+ ],
+ "name": "RoleGranted",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "bytes32",
+ "name": "role",
+ "type": "bytes32"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "account",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "sender",
+ "type": "address"
+ }
+ ],
+ "name": "RoleRevoked",
+ "type": "event"
+ },
+ {
+ "inputs": [],
+ "name": "CANCELLER_ROLE",
+ "outputs": [
+ {
+ "internalType": "bytes32",
+ "name": "",
+ "type": "bytes32"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "DEFAULT_ADMIN_ROLE",
+ "outputs": [
+ {
+ "internalType": "bytes32",
+ "name": "",
+ "type": "bytes32"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "EXECUTOR_ROLE",
+ "outputs": [
+ {
+ "internalType": "bytes32",
+ "name": "",
+ "type": "bytes32"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "PROPOSER_ROLE",
+ "outputs": [
+ {
+ "internalType": "bytes32",
+ "name": "",
+ "type": "bytes32"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "TIMELOCK_ADMIN_ROLE",
+ "outputs": [
+ {
+ "internalType": "bytes32",
+ "name": "",
+ "type": "bytes32"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes32",
+ "name": "id",
+ "type": "bytes32"
+ }
+ ],
+ "name": "cancel",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "target",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "value",
+ "type": "uint256"
+ },
+ {
+ "internalType": "bytes",
+ "name": "payload",
+ "type": "bytes"
+ },
+ {
+ "internalType": "bytes32",
+ "name": "predecessor",
+ "type": "bytes32"
+ },
+ {
+ "internalType": "bytes32",
+ "name": "salt",
+ "type": "bytes32"
+ }
+ ],
+ "name": "execute",
+ "outputs": [],
+ "stateMutability": "payable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address[]",
+ "name": "targets",
+ "type": "address[]"
+ },
+ {
+ "internalType": "uint256[]",
+ "name": "values",
+ "type": "uint256[]"
+ },
+ {
+ "internalType": "bytes[]",
+ "name": "payloads",
+ "type": "bytes[]"
+ },
+ {
+ "internalType": "bytes32",
+ "name": "predecessor",
+ "type": "bytes32"
+ },
+ {
+ "internalType": "bytes32",
+ "name": "salt",
+ "type": "bytes32"
+ }
+ ],
+ "name": "executeBatch",
+ "outputs": [],
+ "stateMutability": "payable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "getMinDelay",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes32",
+ "name": "role",
+ "type": "bytes32"
+ }
+ ],
+ "name": "getRoleAdmin",
+ "outputs": [
+ {
+ "internalType": "bytes32",
+ "name": "",
+ "type": "bytes32"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes32",
+ "name": "id",
+ "type": "bytes32"
+ }
+ ],
+ "name": "getTimestamp",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes32",
+ "name": "role",
+ "type": "bytes32"
+ },
+ {
+ "internalType": "address",
+ "name": "account",
+ "type": "address"
+ }
+ ],
+ "name": "grantRole",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes32",
+ "name": "role",
+ "type": "bytes32"
+ },
+ {
+ "internalType": "address",
+ "name": "account",
+ "type": "address"
+ }
+ ],
+ "name": "hasRole",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "target",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "value",
+ "type": "uint256"
+ },
+ {
+ "internalType": "bytes",
+ "name": "data",
+ "type": "bytes"
+ },
+ {
+ "internalType": "bytes32",
+ "name": "predecessor",
+ "type": "bytes32"
+ },
+ {
+ "internalType": "bytes32",
+ "name": "salt",
+ "type": "bytes32"
+ }
+ ],
+ "name": "hashOperation",
+ "outputs": [
+ {
+ "internalType": "bytes32",
+ "name": "",
+ "type": "bytes32"
+ }
+ ],
+ "stateMutability": "pure",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address[]",
+ "name": "targets",
+ "type": "address[]"
+ },
+ {
+ "internalType": "uint256[]",
+ "name": "values",
+ "type": "uint256[]"
+ },
+ {
+ "internalType": "bytes[]",
+ "name": "payloads",
+ "type": "bytes[]"
+ },
+ {
+ "internalType": "bytes32",
+ "name": "predecessor",
+ "type": "bytes32"
+ },
+ {
+ "internalType": "bytes32",
+ "name": "salt",
+ "type": "bytes32"
+ }
+ ],
+ "name": "hashOperationBatch",
+ "outputs": [
+ {
+ "internalType": "bytes32",
+ "name": "",
+ "type": "bytes32"
+ }
+ ],
+ "stateMutability": "pure",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes32",
+ "name": "id",
+ "type": "bytes32"
+ }
+ ],
+ "name": "isOperation",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes32",
+ "name": "id",
+ "type": "bytes32"
+ }
+ ],
+ "name": "isOperationDone",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes32",
+ "name": "id",
+ "type": "bytes32"
+ }
+ ],
+ "name": "isOperationPending",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes32",
+ "name": "id",
+ "type": "bytes32"
+ }
+ ],
+ "name": "isOperationReady",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256[]",
+ "name": "",
+ "type": "uint256[]"
+ },
+ {
+ "internalType": "uint256[]",
+ "name": "",
+ "type": "uint256[]"
+ },
+ {
+ "internalType": "bytes",
+ "name": "",
+ "type": "bytes"
+ }
+ ],
+ "name": "onERC1155BatchReceived",
+ "outputs": [
+ {
+ "internalType": "bytes4",
+ "name": "",
+ "type": "bytes4"
+ }
+ ],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ },
+ {
+ "internalType": "bytes",
+ "name": "",
+ "type": "bytes"
+ }
+ ],
+ "name": "onERC1155Received",
+ "outputs": [
+ {
+ "internalType": "bytes4",
+ "name": "",
+ "type": "bytes4"
+ }
+ ],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ },
+ {
+ "internalType": "bytes",
+ "name": "",
+ "type": "bytes"
+ }
+ ],
+ "name": "onERC721Received",
+ "outputs": [
+ {
+ "internalType": "bytes4",
+ "name": "",
+ "type": "bytes4"
+ }
+ ],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes32",
+ "name": "role",
+ "type": "bytes32"
+ },
+ {
+ "internalType": "address",
+ "name": "account",
+ "type": "address"
+ }
+ ],
+ "name": "renounceRole",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes32",
+ "name": "role",
+ "type": "bytes32"
+ },
+ {
+ "internalType": "address",
+ "name": "account",
+ "type": "address"
+ }
+ ],
+ "name": "revokeRole",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "target",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "value",
+ "type": "uint256"
+ },
+ {
+ "internalType": "bytes",
+ "name": "data",
+ "type": "bytes"
+ },
+ {
+ "internalType": "bytes32",
+ "name": "predecessor",
+ "type": "bytes32"
+ },
+ {
+ "internalType": "bytes32",
+ "name": "salt",
+ "type": "bytes32"
+ },
+ {
+ "internalType": "uint256",
+ "name": "delay",
+ "type": "uint256"
+ }
+ ],
+ "name": "schedule",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address[]",
+ "name": "targets",
+ "type": "address[]"
+ },
+ {
+ "internalType": "uint256[]",
+ "name": "values",
+ "type": "uint256[]"
+ },
+ {
+ "internalType": "bytes[]",
+ "name": "payloads",
+ "type": "bytes[]"
+ },
+ {
+ "internalType": "bytes32",
+ "name": "predecessor",
+ "type": "bytes32"
+ },
+ {
+ "internalType": "bytes32",
+ "name": "salt",
+ "type": "bytes32"
+ },
+ {
+ "internalType": "uint256",
+ "name": "delay",
+ "type": "uint256"
+ }
+ ],
+ "name": "scheduleBatch",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes4",
+ "name": "interfaceId",
+ "type": "bytes4"
+ }
+ ],
+ "name": "supportsInterface",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "newDelay",
+ "type": "uint256"
+ }
+ ],
+ "name": "updateDelay",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "stateMutability": "payable",
+ "type": "receive"
+ }
+]
\ No newline at end of file
diff --git a/contracts/abi/ZkEvmV2.abi b/contracts/abi/ZkEvmV2.abi
new file mode 100644
index 000000000..b5c9f54e1
--- /dev/null
+++ b/contracts/abi/ZkEvmV2.abi
@@ -0,0 +1,1272 @@
+[
+ {
+ "inputs": [],
+ "stateMutability": "nonpayable",
+ "type": "constructor"
+ },
+ {
+ "inputs": [],
+ "name": "BlockTimestampError",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "EmptyBlock",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "recipient",
+ "type": "address"
+ }
+ ],
+ "name": "FeePaymentFailed",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "FeeTooLow",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "InvalidProof",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "InvalidProofType",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes32",
+ "name": "pauseType",
+ "type": "bytes32"
+ }
+ ],
+ "name": "IsNotPaused",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes32",
+ "name": "pauseType",
+ "type": "bytes32"
+ }
+ ],
+ "name": "IsPaused",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes32",
+ "name": "messageHash",
+ "type": "bytes32"
+ }
+ ],
+ "name": "L1L2MessageNotSent",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "LimitIsZero",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "inde",
+ "type": "uint256"
+ }
+ ],
+ "name": "MemoryOutOfBounds",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes32",
+ "name": "messageHash",
+ "type": "bytes32"
+ }
+ ],
+ "name": "MessageAlreadyReceived",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "MessageAlreadySent",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "MessageDoesNotExistOrHasAlreadyBeenClaimed",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "destination",
+ "type": "address"
+ }
+ ],
+ "name": "MessageSendingFailed",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "NotList",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "PeriodIsZero",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "ProofIsEmpty",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "RateLimitExceeded",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "StartingRootHashDoesNotMatch",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "TransactionShort",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "UnknownTransactionType",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "ValueSentTooLow",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "ValueShouldBeGreaterThanFee",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "WrongBytesLength",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "ZeroAddressNotAllowed",
+ "type": "error"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "resettingAddress",
+ "type": "address"
+ }
+ ],
+ "name": "AmountUsedInPeriodReset",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "uint256",
+ "name": "blockNumber",
+ "type": "uint256"
+ },
+ {
+ "indexed": true,
+ "internalType": "bytes32",
+ "name": "stateRootHash",
+ "type": "bytes32"
+ }
+ ],
+ "name": "BlockFinalized",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "uint256",
+ "name": "lastBlockFinalized",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "bytes32",
+ "name": "startingRootHash",
+ "type": "bytes32"
+ },
+ {
+ "indexed": false,
+ "internalType": "bytes32",
+ "name": "finalRootHash",
+ "type": "bytes32"
+ }
+ ],
+ "name": "BlocksVerificationDone",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "uint8",
+ "name": "version",
+ "type": "uint8"
+ }
+ ],
+ "name": "Initialized",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "bytes32[]",
+ "name": "messageHashes",
+ "type": "bytes32[]"
+ }
+ ],
+ "name": "L1L2MessagesReceivedOnL2",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "bytes32",
+ "name": "messageHash",
+ "type": "bytes32"
+ }
+ ],
+ "name": "L2L1MessageHashAddedToInbox",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "amountChangeBy",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "bool",
+ "name": "amountUsedLoweredToLimit",
+ "type": "bool"
+ },
+ {
+ "indexed": false,
+ "internalType": "bool",
+ "name": "usedAmountResetToZero",
+ "type": "bool"
+ }
+ ],
+ "name": "LimitAmountChanged",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "bytes32",
+ "name": "_messageHash",
+ "type": "bytes32"
+ }
+ ],
+ "name": "MessageClaimed",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "_from",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "_to",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "_fee",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "_value",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "_nonce",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "bytes",
+ "name": "_calldata",
+ "type": "bytes"
+ },
+ {
+ "indexed": true,
+ "internalType": "bytes32",
+ "name": "_messageHash",
+ "type": "bytes32"
+ }
+ ],
+ "name": "MessageSent",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "messageSender",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "bytes32",
+ "name": "pauseType",
+ "type": "bytes32"
+ }
+ ],
+ "name": "Paused",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "bytes32",
+ "name": "role",
+ "type": "bytes32"
+ },
+ {
+ "indexed": true,
+ "internalType": "bytes32",
+ "name": "previousAdminRole",
+ "type": "bytes32"
+ },
+ {
+ "indexed": true,
+ "internalType": "bytes32",
+ "name": "newAdminRole",
+ "type": "bytes32"
+ }
+ ],
+ "name": "RoleAdminChanged",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "bytes32",
+ "name": "role",
+ "type": "bytes32"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "account",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "sender",
+ "type": "address"
+ }
+ ],
+ "name": "RoleGranted",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "bytes32",
+ "name": "role",
+ "type": "bytes32"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "account",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "sender",
+ "type": "address"
+ }
+ ],
+ "name": "RoleRevoked",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "messageSender",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "bytes32",
+ "name": "pauseType",
+ "type": "bytes32"
+ }
+ ],
+ "name": "UnPaused",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "verifierAddress",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "uint256",
+ "name": "proofType",
+ "type": "uint256"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "verifierSetBy",
+ "type": "address"
+ }
+ ],
+ "name": "VerifierAddressChanged",
+ "type": "event"
+ },
+ {
+ "inputs": [],
+ "name": "DEFAULT_ADMIN_ROLE",
+ "outputs": [
+ {
+ "internalType": "bytes32",
+ "name": "",
+ "type": "bytes32"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "GENERAL_PAUSE_TYPE",
+ "outputs": [
+ {
+ "internalType": "bytes32",
+ "name": "",
+ "type": "bytes32"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "INBOX_STATUS_RECEIVED",
+ "outputs": [
+ {
+ "internalType": "uint8",
+ "name": "",
+ "type": "uint8"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "INBOX_STATUS_UNKNOWN",
+ "outputs": [
+ {
+ "internalType": "uint8",
+ "name": "",
+ "type": "uint8"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "L1_L2_PAUSE_TYPE",
+ "outputs": [
+ {
+ "internalType": "bytes32",
+ "name": "",
+ "type": "bytes32"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "L2_L1_PAUSE_TYPE",
+ "outputs": [
+ {
+ "internalType": "bytes32",
+ "name": "",
+ "type": "bytes32"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "OPERATOR_ROLE",
+ "outputs": [
+ {
+ "internalType": "bytes32",
+ "name": "",
+ "type": "bytes32"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "OUTBOX_STATUS_RECEIVED",
+ "outputs": [
+ {
+ "internalType": "uint8",
+ "name": "",
+ "type": "uint8"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "OUTBOX_STATUS_SENT",
+ "outputs": [
+ {
+ "internalType": "uint8",
+ "name": "",
+ "type": "uint8"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "OUTBOX_STATUS_UNKNOWN",
+ "outputs": [
+ {
+ "internalType": "uint8",
+ "name": "",
+ "type": "uint8"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "PAUSE_MANAGER_ROLE",
+ "outputs": [
+ {
+ "internalType": "bytes32",
+ "name": "",
+ "type": "bytes32"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "PROVING_SYSTEM_PAUSE_TYPE",
+ "outputs": [
+ {
+ "internalType": "bytes32",
+ "name": "",
+ "type": "bytes32"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "RATE_LIMIT_SETTER_ROLE",
+ "outputs": [
+ {
+ "internalType": "bytes32",
+ "name": "",
+ "type": "bytes32"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "_from",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "_to",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "_fee",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "_value",
+ "type": "uint256"
+ },
+ {
+ "internalType": "address payable",
+ "name": "_feeRecipient",
+ "type": "address"
+ },
+ {
+ "internalType": "bytes",
+ "name": "_calldata",
+ "type": "bytes"
+ },
+ {
+ "internalType": "uint256",
+ "name": "_nonce",
+ "type": "uint256"
+ }
+ ],
+ "name": "claimMessage",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "currentL2BlockNumber",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "currentPeriodAmountInWei",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "currentPeriodEnd",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "currentTimestamp",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "components": [
+ {
+ "internalType": "bytes32",
+ "name": "blockRootHash",
+ "type": "bytes32"
+ },
+ {
+ "internalType": "uint32",
+ "name": "l2BlockTimestamp",
+ "type": "uint32"
+ },
+ {
+ "internalType": "bytes[]",
+ "name": "transactions",
+ "type": "bytes[]"
+ },
+ {
+ "internalType": "bytes32[]",
+ "name": "l2ToL1MsgHashes",
+ "type": "bytes32[]"
+ },
+ {
+ "internalType": "bytes",
+ "name": "fromAddresses",
+ "type": "bytes"
+ },
+ {
+ "internalType": "uint16[]",
+ "name": "batchReceptionIndices",
+ "type": "uint16[]"
+ }
+ ],
+ "internalType": "struct IZkEvmV2.BlockData[]",
+ "name": "_blocksData",
+ "type": "tuple[]"
+ },
+ {
+ "internalType": "bytes",
+ "name": "_proof",
+ "type": "bytes"
+ },
+ {
+ "internalType": "uint256",
+ "name": "_proofType",
+ "type": "uint256"
+ },
+ {
+ "internalType": "bytes32",
+ "name": "_parentStateRootHash",
+ "type": "bytes32"
+ }
+ ],
+ "name": "finalizeBlocks",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "components": [
+ {
+ "internalType": "bytes32",
+ "name": "blockRootHash",
+ "type": "bytes32"
+ },
+ {
+ "internalType": "uint32",
+ "name": "l2BlockTimestamp",
+ "type": "uint32"
+ },
+ {
+ "internalType": "bytes[]",
+ "name": "transactions",
+ "type": "bytes[]"
+ },
+ {
+ "internalType": "bytes32[]",
+ "name": "l2ToL1MsgHashes",
+ "type": "bytes32[]"
+ },
+ {
+ "internalType": "bytes",
+ "name": "fromAddresses",
+ "type": "bytes"
+ },
+ {
+ "internalType": "uint16[]",
+ "name": "batchReceptionIndices",
+ "type": "uint16[]"
+ }
+ ],
+ "internalType": "struct IZkEvmV2.BlockData[]",
+ "name": "_blocksData",
+ "type": "tuple[]"
+ }
+ ],
+ "name": "finalizeBlocksWithoutProof",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes32",
+ "name": "role",
+ "type": "bytes32"
+ }
+ ],
+ "name": "getRoleAdmin",
+ "outputs": [
+ {
+ "internalType": "bytes32",
+ "name": "",
+ "type": "bytes32"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes32",
+ "name": "role",
+ "type": "bytes32"
+ },
+ {
+ "internalType": "address",
+ "name": "account",
+ "type": "address"
+ }
+ ],
+ "name": "grantRole",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes32",
+ "name": "role",
+ "type": "bytes32"
+ },
+ {
+ "internalType": "address",
+ "name": "account",
+ "type": "address"
+ }
+ ],
+ "name": "hasRole",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes32",
+ "name": "",
+ "type": "bytes32"
+ }
+ ],
+ "name": "inboxL2L1MessageStatus",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes32",
+ "name": "_initialStateRootHash",
+ "type": "bytes32"
+ },
+ {
+ "internalType": "uint256",
+ "name": "_initialL2BlockNumber",
+ "type": "uint256"
+ },
+ {
+ "internalType": "address",
+ "name": "_defaultVerifier",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "_securityCouncil",
+ "type": "address"
+ },
+ {
+ "internalType": "address[]",
+ "name": "_operators",
+ "type": "address[]"
+ },
+ {
+ "internalType": "uint256",
+ "name": "_rateLimitPeriodInSeconds",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "_rateLimitAmountInWei",
+ "type": "uint256"
+ }
+ ],
+ "name": "initialize",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "limitInWei",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "nextMessageNumber",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes32",
+ "name": "",
+ "type": "bytes32"
+ }
+ ],
+ "name": "outboxL1L2MessageStatus",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes32",
+ "name": "_pauseType",
+ "type": "bytes32"
+ }
+ ],
+ "name": "pauseByType",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes32",
+ "name": "",
+ "type": "bytes32"
+ }
+ ],
+ "name": "pauseTypeStatuses",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "periodInSeconds",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes32",
+ "name": "role",
+ "type": "bytes32"
+ },
+ {
+ "internalType": "address",
+ "name": "account",
+ "type": "address"
+ }
+ ],
+ "name": "renounceRole",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "resetAmountUsedInPeriod",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "_amount",
+ "type": "uint256"
+ }
+ ],
+ "name": "resetRateLimitAmount",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes32",
+ "name": "role",
+ "type": "bytes32"
+ },
+ {
+ "internalType": "address",
+ "name": "account",
+ "type": "address"
+ }
+ ],
+ "name": "revokeRole",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "_to",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "_fee",
+ "type": "uint256"
+ },
+ {
+ "internalType": "bytes",
+ "name": "_calldata",
+ "type": "bytes"
+ }
+ ],
+ "name": "sendMessage",
+ "outputs": [],
+ "stateMutability": "payable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "sender",
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "_newVerifierAddress",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "_proofType",
+ "type": "uint256"
+ }
+ ],
+ "name": "setVerifierAddress",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "name": "stateRootHashes",
+ "outputs": [
+ {
+ "internalType": "bytes32",
+ "name": "",
+ "type": "bytes32"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes4",
+ "name": "interfaceId",
+ "type": "bytes4"
+ }
+ ],
+ "name": "supportsInterface",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes32",
+ "name": "_pauseType",
+ "type": "bytes32"
+ }
+ ],
+ "name": "unPauseByType",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "name": "verifiers",
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "stateMutability": "payable",
+ "type": "receive"
+ }
+]
\ No newline at end of file
diff --git a/contracts/common.ts b/contracts/common.ts
new file mode 100644
index 000000000..c4e523473
--- /dev/null
+++ b/contracts/common.ts
@@ -0,0 +1,68 @@
+import fs from "fs";
+
+const MAX_GAS_LIMIT = process.env.TX_GAS_LIMIT ? parseInt(process.env.TX_GAS_LIMIT) : 500000000;
+
+/**
+ * Helper function to deal with account JSON files
+ * @param {string} filePath full path to file with private key
+ */
+function getPrivateKeyFromFile(filePath: string): string {
+ let privateKey;
+ if (fs.existsSync(filePath)) {
+ const account = fs.readFileSync(filePath, "utf8");
+ privateKey = JSON.parse(account).account_key.priv_key;
+ } else {
+ console.warn(`Path ${filePath} with private key does not exist`);
+ }
+ return privateKey;
+}
+
+/**
+ * Get private key of contract owner, based on several assumptions
+ */
+function getContractOwnerPrivateKey(
+ keyFile: string,
+ bridgeOwnerPathOverride: string,
+ bridgeOwnerPrivateKeyOverride: string,
+): string {
+ let contractOwnerJsonPath;
+
+ if (bridgeOwnerPrivateKeyOverride) {
+ return bridgeOwnerPrivateKeyOverride;
+ } else if (bridgeOwnerPathOverride && fs.existsSync(bridgeOwnerPathOverride)) {
+ contractOwnerJsonPath = bridgeOwnerPathOverride;
+ } else {
+ contractOwnerJsonPath = keyFile;
+ }
+ return getPrivateKeyFromFile(contractOwnerJsonPath);
+}
+
+function getBlockchainNode(): string {
+ return process.env.BLOCKCHAIN_NODE || "http://127.0.0.1:8545";
+}
+
+function getL2BlockchainNode(): string | undefined {
+ return process.env.L2_BLOCKCHAIN_NODE;
+}
+
+/**
+ * Returns path to JSON file with address of deployed rollup smart config, which contains address and ABI
+ * @returns {string}
+ */
+function getRollupContractConfigPath(): string {
+ return process.env.SMC_CONFIG_PATH || "/smart_contract/data/smc_config.json";
+}
+
+function getRollupJsonPath(): string {
+ return process.env.ROLLUP_JSON_PATH || "/smart_contract/data/rollup.json";
+}
+
+export {
+ MAX_GAS_LIMIT,
+ getBlockchainNode,
+ getContractOwnerPrivateKey,
+ getL2BlockchainNode,
+ getRollupContractConfigPath,
+ getRollupJsonPath
+};
+
diff --git a/contracts/contracts/ZkEvmV2.sol b/contracts/contracts/ZkEvmV2.sol
new file mode 100644
index 000000000..8af9b84aa
--- /dev/null
+++ b/contracts/contracts/ZkEvmV2.sol
@@ -0,0 +1,319 @@
+// SPDX-License-Identifier: AGPL-3.0
+pragma solidity 0.8.19;
+
+import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
+import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
+import { L1MessageService } from "./messageService/l1/L1MessageService.sol";
+import { TransactionDecoder } from "./messageService/lib/TransactionDecoder.sol";
+import { IZkEvmV2 } from "./interfaces/IZkEvmV2.sol";
+import { IPlonkVerifier } from "./interfaces/IPlonkVerifier.sol";
+import { CodecV2 } from "./messageService/lib/Codec.sol";
+
+/**
+ * @title Contract to manage cross-chain messaging on L1 and rollup proving.
+ * @author ConsenSys Software Inc.
+ */
+contract ZkEvmV2 is IZkEvmV2, Initializable, AccessControlUpgradeable, L1MessageService {
+ using TransactionDecoder for *;
+ using CodecV2 for *;
+
+ uint256 private constant MODULO_R = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
+ bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
+
+ uint256 public currentTimestamp;
+ uint256 public currentL2BlockNumber;
+
+ mapping(uint256 => bytes32) public stateRootHashes;
+ mapping(uint256 => address) public verifiers;
+
+ uint256[50] private __gap;
+
+ /// @custom:oz-upgrades-unsafe-allow constructor
+ constructor() {
+ _disableInitializers();
+ }
+
+ /**
+ * @notice Initializes zkEvm and underlying service dependencies.
+ * @dev DEFAULT_ADMIN_ROLE is set for the security council.
+ * @dev OPERATOR_ROLE is set for operators.
+ * @param _initialStateRootHash The initial hash at migration used for proof verification.
+ * @param _initialL2BlockNumber The initial block number at migration.
+ * @param _defaultVerifier The default verifier for rollup proofs.
+ * @param _securityCouncil The address for the security council performing admin operations.
+ * @param _operators The allowed rollup operators at initialization.
+ * @param _rateLimitPeriodInSeconds The period in which withdrawal amounts and fees will be accumulated.
+ * @param _rateLimitAmountInWei The limit allowed for withdrawing in the period.
+ **/
+ function initialize(
+ bytes32 _initialStateRootHash,
+ uint256 _initialL2BlockNumber,
+ address _defaultVerifier,
+ address _securityCouncil,
+ address[] calldata _operators,
+ uint256 _rateLimitPeriodInSeconds,
+ uint256 _rateLimitAmountInWei
+ ) public initializer {
+ if (_defaultVerifier == address(0)) {
+ revert ZeroAddressNotAllowed();
+ }
+
+ for (uint256 i; i < _operators.length; ) {
+ if (_operators[i] == address(0)) {
+ revert ZeroAddressNotAllowed();
+ }
+ _grantRole(OPERATOR_ROLE, _operators[i]);
+ unchecked {
+ i++;
+ }
+ }
+
+ _grantRole(DEFAULT_ADMIN_ROLE, _securityCouncil);
+
+ __MessageService_init(_securityCouncil, _securityCouncil, _rateLimitPeriodInSeconds, _rateLimitAmountInWei);
+
+ verifiers[0] = _defaultVerifier;
+ currentL2BlockNumber = _initialL2BlockNumber;
+ stateRootHashes[_initialL2BlockNumber] = _initialStateRootHash;
+ }
+
+ /**
+ * @notice Adds or updates the verifier contract address for a proof type.
+ * @dev DEFAULT_ADMIN_ROLE is required to execute.
+ * @param _newVerifierAddress The address for the verifier contract.
+ * @param _proofType The proof type being set/updated.
+ **/
+ function setVerifierAddress(address _newVerifierAddress, uint256 _proofType) external onlyRole(DEFAULT_ADMIN_ROLE) {
+ if (_newVerifierAddress == address(0)) {
+ revert ZeroAddressNotAllowed();
+ }
+
+ emit VerifierAddressChanged(_newVerifierAddress, _proofType, msg.sender);
+
+ verifiers[_proofType] = _newVerifierAddress;
+ }
+
+ /**
+ * @notice Finalizes blocks without using a proof.
+ * @dev DEFAULT_ADMIN_ROLE is required to execute.
+ * @dev _blocksData[0].fromAddresses is a temporary workaround to pass bytes calldata
+ * @param _blocksData The full BlockData collection - block, transaction and log data.
+ **/
+ function finalizeBlocksWithoutProof(
+ BlockData[] calldata _blocksData
+ ) external whenTypeNotPaused(GENERAL_PAUSE_TYPE) onlyRole(DEFAULT_ADMIN_ROLE) {
+ _finalizeBlocks(_blocksData, _blocksData[0].fromAddresses, 0, bytes32(0), false);
+ }
+
+ /**
+ * @notice Finalizes blocks using a proof.
+ * @dev OPERATOR_ROLE is required to execute.
+ * @dev If the verifier based on proof type is not found, it reverts.
+ * @param _blocksData The full BlockData collection - block, transaction and log data.
+ * @param _proof The proof to be verified with the proof type verifier contract.
+ * @param _proofType The proof type to determine which verifier contract to use.
+ * @param _parentStateRootHash The starting roothash for the last known block.
+ **/
+ function finalizeBlocks(
+ BlockData[] calldata _blocksData,
+ bytes calldata _proof,
+ uint256 _proofType,
+ bytes32 _parentStateRootHash
+ )
+ external
+ whenTypeNotPaused(PROVING_SYSTEM_PAUSE_TYPE)
+ whenTypeNotPaused(GENERAL_PAUSE_TYPE)
+ onlyRole(OPERATOR_ROLE)
+ {
+ if (stateRootHashes[currentL2BlockNumber] != _parentStateRootHash) {
+ revert StartingRootHashDoesNotMatch();
+ }
+
+ _finalizeBlocks(_blocksData, _proof, _proofType, _parentStateRootHash, true);
+ }
+
+ /**
+ * @notice Finalizes blocks with or without using a proof depending on _shouldProve
+ * @dev If the verifier based on proof type is not found, it reverts.
+ * @param _blocksData The full BlockData collection - block, transaction and log data.
+ * @param _proof The proof to be verified with the proof type verifier contract.
+ * @param _proofType The proof type to determine which verifier contract to use.
+ * @param _parentStateRootHash The starting roothash for the last known block.
+ **/
+ function _finalizeBlocks(
+ BlockData[] calldata _blocksData,
+ bytes calldata _proof,
+ uint256 _proofType,
+ bytes32 _parentStateRootHash,
+ bool _shouldProve
+ ) private {
+ if (_blocksData.length == 0) {
+ revert EmptyBlockDataArray();
+ }
+
+ uint256 currentBlockNumberTemp = currentL2BlockNumber;
+
+ uint256 firstBlockNumber;
+ unchecked {
+ firstBlockNumber = currentBlockNumberTemp + 1;
+ }
+
+ uint256[] memory timestamps = new uint256[](_blocksData.length);
+ bytes32[] memory blockHashes = new bytes32[](_blocksData.length);
+ bytes32[] memory hashOfRootHashes;
+
+ unchecked {
+ hashOfRootHashes = new bytes32[](_blocksData.length + 1);
+ }
+
+ hashOfRootHashes[0] = _parentStateRootHash;
+
+ bytes32 hashOfTxHashes;
+ bytes32 hashOfMessageHashes;
+
+ for (uint256 i; i < _blocksData.length; ) {
+ BlockData calldata blockInfo = _blocksData[i];
+
+ if (blockInfo.l2BlockTimestamp >= block.timestamp) {
+ revert BlockTimestampError();
+ }
+
+ hashOfTxHashes = _processBlockTransactions(blockInfo.transactions, blockInfo.batchReceptionIndices);
+ hashOfMessageHashes = _processMessageHashes(blockInfo.l2ToL1MsgHashes);
+
+ unchecked {
+ ++currentBlockNumberTemp;
+ }
+
+ blockHashes[i] = keccak256(
+ abi.encodePacked(
+ hashOfTxHashes,
+ hashOfMessageHashes,
+ keccak256(abi.encodePacked(blockInfo.batchReceptionIndices)),
+ keccak256(blockInfo.fromAddresses)
+ )
+ );
+
+ timestamps[i] = blockInfo.l2BlockTimestamp;
+
+ unchecked {
+ hashOfRootHashes[i + 1] = blockInfo.blockRootHash;
+ }
+
+ emit BlockFinalized(currentBlockNumberTemp, blockInfo.blockRootHash);
+
+ unchecked {
+ i++;
+ }
+ }
+
+ unchecked {
+ uint256 arrayIndex = _blocksData.length - 1;
+ stateRootHashes[currentBlockNumberTemp] = _blocksData[arrayIndex].blockRootHash;
+ currentTimestamp = _blocksData[arrayIndex].l2BlockTimestamp;
+ currentL2BlockNumber = currentBlockNumberTemp;
+ }
+
+ if (_shouldProve) {
+ uint256 publicInput = uint256(
+ keccak256(
+ abi.encode(
+ keccak256(abi.encodePacked(blockHashes)),
+ firstBlockNumber,
+ keccak256(abi.encodePacked(timestamps)),
+ keccak256(abi.encodePacked(hashOfRootHashes))
+ )
+ )
+ );
+
+ assembly {
+ publicInput := mod(publicInput, MODULO_R)
+ }
+
+ _verifyProof(publicInput, _proofType, _proof, _parentStateRootHash);
+ }
+ }
+
+ /**
+ * @notice Hashes all transactions individually and then hashes the packed hash array.
+ * @dev Updates the outbox status on L1 as received.
+ * @param _transactions The transactions in a particular block.
+ * @param _batchReceptionIndices The indexes where the transaction type is the L1->L2 achoring message hashes transaction.
+ **/
+ function _processBlockTransactions(
+ bytes[] calldata _transactions,
+ uint16[] calldata _batchReceptionIndices
+ ) internal returns (bytes32 hashOfTxHashes) {
+ bytes32[] memory transactionHashes = new bytes32[](_transactions.length);
+
+ if (_transactions.length == 0) {
+ revert EmptyBlock();
+ }
+
+ for (uint256 i; i < _batchReceptionIndices.length; ) {
+ _updateL1L2MessageStatusToReceived(
+ TransactionDecoder.decodeTransaction(_transactions[_batchReceptionIndices[i]])._extractXDomainAddHashes()
+ );
+
+ unchecked {
+ i++;
+ }
+ }
+
+ for (uint256 i; i < _transactions.length; ) {
+ transactionHashes[i] = keccak256(_transactions[i]);
+
+ unchecked {
+ i++;
+ }
+ }
+ hashOfTxHashes = keccak256(abi.encodePacked(transactionHashes));
+ }
+
+ /**
+ * @notice Anchors message hashes and hashes the packed hash array.
+ * @dev Also adds L2->L1 sent message hashes for later claiming.
+ * @param _messageHashes The hashes in the message sent event logs.
+ **/
+ function _processMessageHashes(bytes32[] calldata _messageHashes) internal returns (bytes32 hashOfLogHashes) {
+ for (uint256 i; i < _messageHashes.length; ) {
+ _addL2L1MessageHash(_messageHashes[i]);
+
+ unchecked {
+ i++;
+ }
+ }
+ hashOfLogHashes = keccak256(abi.encodePacked(_messageHashes));
+ }
+
+ /**
+ * @notice Verifies the proof with locally computed public inputs.
+ * @dev If the verifier based on proof type is not found, it reverts with InvalidProofType.
+ * @param _publicInputHash The full BlockData collection - block, transaction and log data.
+ * @param _proofType The proof type to determine which verifier contract to use.
+ * @param _proof The proof to be verified with the proof type verifier contract.
+ * @param _parentStateRootHash The beginning roothash to start with.
+ **/
+ function _verifyProof(
+ uint256 _publicInputHash,
+ uint256 _proofType,
+ bytes calldata _proof,
+ bytes32 _parentStateRootHash
+ ) private {
+ uint256[] memory input = new uint256[](1);
+ input[0] = _publicInputHash;
+
+ address verifierToUse = verifiers[_proofType];
+
+ if (verifierToUse == address(0)) {
+ revert InvalidProofType();
+ }
+
+ bool success = IPlonkVerifier(verifierToUse).Verify(_proof, input);
+ if (!success) {
+ revert InvalidProof();
+ }
+
+ emit BlocksVerificationDone(currentL2BlockNumber, _parentStateRootHash, stateRootHashes[currentL2BlockNumber]);
+ }
+}
diff --git a/contracts/contracts/ZkEvmV2Init.sol b/contracts/contracts/ZkEvmV2Init.sol
new file mode 100644
index 000000000..1a6629476
--- /dev/null
+++ b/contracts/contracts/ZkEvmV2Init.sol
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: AGPL-3.0
+pragma solidity 0.8.19;
+
+import { ZkEvmV2 } from "./ZkEvmV2.sol";
+
+/**
+ * @title Contract to reinitialize cross-chain messaging on L1 and rollup proving.
+ * @author ConsenSys Software Inc.
+ * @dev Init indicates it is an initializer contract
+ */
+contract ZkEvmV2Init is ZkEvmV2 {
+ /*
+ * @notice Reinitializes zkEvm and underlying service dependencies.
+ * @param _initialStateRootHash The initial hash at migration used for proof verification.
+ * @param _initialL2BlockNumber The initial block number at migration.
+ **/
+ function initializeV2(uint256 _initialL2BlockNumber, bytes32 _initialStateRootHash) public reinitializer(3) {
+ currentL2BlockNumber = _initialL2BlockNumber;
+ stateRootHashes[_initialL2BlockNumber] = _initialStateRootHash;
+ }
+}
diff --git a/contracts/contracts/interfaces/IGenericErrors.sol b/contracts/contracts/interfaces/IGenericErrors.sol
new file mode 100644
index 000000000..8c7d52ae3
--- /dev/null
+++ b/contracts/contracts/interfaces/IGenericErrors.sol
@@ -0,0 +1,9 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity 0.8.19;
+
+interface IGenericErrors {
+ /**
+ * @dev Thrown when a parameter is the zero address.
+ */
+ error ZeroAddressNotAllowed();
+}
diff --git a/contracts/contracts/interfaces/IL1MessageManager.sol b/contracts/contracts/interfaces/IL1MessageManager.sol
new file mode 100644
index 000000000..efb33db8c
--- /dev/null
+++ b/contracts/contracts/interfaces/IL1MessageManager.sol
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity 0.8.19;
+
+interface IL1MessageManager {
+ /**
+ * @dev Emitted when L2->L1 message hashes have been added to L1 storage.
+ */
+ event L2L1MessageHashAddedToInbox(bytes32 indexed messageHash);
+
+ /**
+ * @dev Emitted when L1->L2 messages have been anchored on L2 and updated on L1.
+ */
+ event L1L2MessagesReceivedOnL2(bytes32[] messageHashes);
+
+ /**
+ * @dev Thrown when the message has been already sent.
+ */
+ error MessageAlreadySent();
+
+ /**
+ * @dev Thrown when the message has already been claimed.
+ */
+ error MessageDoesNotExistOrHasAlreadyBeenClaimed();
+
+ /**
+ * @dev Thrown when the message has already been received.
+ */
+ error MessageAlreadyReceived(bytes32 messageHash);
+
+ /**
+ * @dev Thrown when the L1->L2 message has not been sent.
+ */
+ error L1L2MessageNotSent(bytes32 messageHash);
+}
diff --git a/contracts/contracts/interfaces/IL2MessageManager.sol b/contracts/contracts/interfaces/IL2MessageManager.sol
new file mode 100644
index 000000000..04e103f07
--- /dev/null
+++ b/contracts/contracts/interfaces/IL2MessageManager.sol
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity 0.8.19;
+
+interface IL2MessageManager {
+ /**
+ * @dev Emitted when L1->L2 message hashes have been added to L2 storage.
+ */
+ event L1L2MessageHashesAddedToInbox(bytes32[] messageHashes);
+
+ /**
+ * @dev Thrown when the message hashes list length is higher than one hundred.
+ */
+ error MessageHashesListLengthHigherThanOneHundred(uint256 length);
+
+ /**
+ * @dev Thrown when the message hashes array is empty.
+ */
+ error EmptyMessageHashesArray();
+
+ /**
+ * @dev Thrown when the message does not exist or has already been claimed.
+ */
+ error MessageDoesNotExistOrHasAlreadyBeenClaimed();
+
+ /**
+ * @notice Anchor L1-> L2 message hashes.
+ * @param _messageHashes New message hashes to anchor on L2.
+ */
+ function addL1L2MessageHashes(bytes32[] calldata _messageHashes) external;
+}
diff --git a/contracts/contracts/interfaces/IMessageService.sol b/contracts/contracts/interfaces/IMessageService.sol
new file mode 100644
index 000000000..4e34a922f
--- /dev/null
+++ b/contracts/contracts/interfaces/IMessageService.sol
@@ -0,0 +1,85 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity 0.8.19;
+
+interface IMessageService {
+ /**
+ * @dev Emitted when a message is sent.
+ * @dev We include the message hash to save hashing costs on the rollup.
+ */
+ event MessageSent(
+ address indexed _from,
+ address indexed _to,
+ uint256 _fee,
+ uint256 _value,
+ uint256 _nonce,
+ bytes _calldata,
+ bytes32 indexed _messageHash
+ );
+
+ /**
+ * @dev Emitted when a message is claimed.
+ */
+ event MessageClaimed(bytes32 indexed _messageHash);
+
+ /**
+ * @dev Thrown when fees are lower than the minimum fee.
+ */
+ error FeeTooLow();
+
+ /**
+ * @dev Thrown when fees are lower than value.
+ */
+ error ValueShouldBeGreaterThanFee();
+
+ /**
+ * @dev Thrown when the value sent is less than the fee.
+ * @dev Value to forward on is msg.value - _fee.
+ */
+ error ValueSentTooLow();
+
+ /**
+ * @dev Thrown when the destination address reverts.
+ */
+ error MessageSendingFailed(address destination);
+
+ /**
+ * @dev Thrown when the destination address reverts.
+ */
+ error FeePaymentFailed(address recipient);
+
+ /**
+ * @notice Sends a message for transporting from the given chain.
+ * @dev This function should be called with a msg.value = _value + _fee. The fee will be paid on the destination chain.
+ * @param _to The destination address on the destination chain.
+ * @param _fee The message service fee on the origin chain.
+ * @param _calldata The calldata used by the destination message service to call the destination contract.
+ */
+ function sendMessage(address _to, uint256 _fee, bytes calldata _calldata) external payable;
+
+ /**
+ * @notice Deliver a message to the destination chain.
+ * @notice Is called automatically by the Postman, dApp or end user.
+ * @param _from The msg.sender calling the origin message service.
+ * @param _to The destination address on the destination chain.
+ * @param _value The value to be transferred to the destination address.
+ * @param _fee The message service fee on the origin chain.
+ * @param _feeRecipient Address that will receive the fees.
+ * @param _calldata The calldata used by the destination message service to call/forward to the destination contract.
+ * @param _nonce Unique message number.
+ */
+ function claimMessage(
+ address _from,
+ address _to,
+ uint256 _fee,
+ uint256 _value,
+ address payable _feeRecipient,
+ bytes calldata _calldata,
+ uint256 _nonce
+ ) external;
+
+ /**
+ * @notice Returns the original sender of the message on the origin layer.
+ * @return The original sender of the message on the origin layer.
+ */
+ function sender() external view returns (address);
+}
diff --git a/contracts/contracts/interfaces/IPauseManager.sol b/contracts/contracts/interfaces/IPauseManager.sol
new file mode 100644
index 000000000..27929a447
--- /dev/null
+++ b/contracts/contracts/interfaces/IPauseManager.sol
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity 0.8.19;
+
+interface IPauseManager {
+ /**
+ * @dev Thrown when a specific pause type is paused.
+ */
+ error IsPaused(bytes32 pauseType);
+
+ /**
+ * @dev Thrown when a specific pause type is not paused and expected to be.
+ */
+ error IsNotPaused(bytes32 pauseType);
+
+ /**
+ * @dev Emitted when a pause type is paused.
+ */
+ event Paused(address messageSender, bytes32 pauseType);
+
+ /**
+ * @dev Emitted when a pause type is unpaused.
+ */
+ event UnPaused(address messageSender, bytes32 pauseType);
+}
diff --git a/contracts/contracts/interfaces/IPlonkVerifier.sol b/contracts/contracts/interfaces/IPlonkVerifier.sol
new file mode 100644
index 000000000..95c028aaf
--- /dev/null
+++ b/contracts/contracts/interfaces/IPlonkVerifier.sol
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity 0.8.19;
+
+/**
+ * @title Contract to manage cross-chain messaging on L1 and rollup proving
+ * @author ConsenSys Software Inc.
+ */
+interface IPlonkVerifier {
+ /**
+ * @notice Interface for verifier contracts.
+ * @param _proof The proof used to verify.
+ * @param _public_inputs The computed public inputs for the proof verification.
+ */
+ function Verify(bytes calldata _proof, uint256[] calldata _public_inputs) external returns (bool);
+}
diff --git a/contracts/contracts/interfaces/IRateLimiter.sol b/contracts/contracts/interfaces/IRateLimiter.sol
new file mode 100644
index 000000000..ba9c19d65
--- /dev/null
+++ b/contracts/contracts/interfaces/IRateLimiter.sol
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity 0.8.19;
+
+interface IRateLimiter {
+ /**
+ * @dev Thrown when an amount breaches the limit in the period.
+ */
+ error RateLimitExceeded();
+
+ /**
+ * @dev Thrown when the period is initialised to zero.
+ */
+ error PeriodIsZero();
+
+ /**
+ * @dev Thrown when the limit is initialised to zero.
+ */
+ error LimitIsZero();
+
+ /**
+ * @dev Emitted when the amount in the period is reset to zero.
+ */
+ event AmountUsedInPeriodReset(address indexed resettingAddress);
+
+ /**
+ * @dev Emitted when the limit is changed.
+ * @dev If the current used amount is higher than the new limit, the used amount is lowered to the limit.
+ */
+ event LimitAmountChanged(
+ address indexed amountChangeBy,
+ uint256 amount,
+ bool amountUsedLoweredToLimit,
+ bool usedAmountResetToZero
+ );
+
+ /**
+ * @notice Resets the rate limit amount to the amount specified.
+ * @param _amount New message hashes.
+ */
+ function resetRateLimitAmount(uint256 _amount) external;
+
+ /**
+ * @notice Resets the amount used in the period to zero.
+ */
+ function resetAmountUsedInPeriod() external;
+}
diff --git a/contracts/contracts/interfaces/IZkEvmV2.sol b/contracts/contracts/interfaces/IZkEvmV2.sol
new file mode 100644
index 000000000..c3113b33d
--- /dev/null
+++ b/contracts/contracts/interfaces/IZkEvmV2.sol
@@ -0,0 +1,97 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity 0.8.19;
+
+interface IZkEvmV2 {
+ struct BlockData {
+ bytes32 blockRootHash;
+ uint32 l2BlockTimestamp;
+ bytes[] transactions;
+ bytes32[] l2ToL1MsgHashes;
+ bytes fromAddresses;
+ uint16[] batchReceptionIndices;
+ }
+
+ /**
+ * @dev Emitted when a L2 block has been finalized on L1
+ */
+ event BlockFinalized(uint256 indexed blockNumber, bytes32 indexed stateRootHash);
+ /**
+ * @dev Emitted when a L2 blocks have been finalized on L1
+ */
+ event BlocksVerificationDone(uint256 indexed lastBlockFinalized, bytes32 startingRootHash, bytes32 finalRootHash);
+
+ /**
+ * @dev Emitted when a verifier is set for a particular proof type
+ */
+ event VerifierAddressChanged(
+ address indexed verifierAddress,
+ uint256 indexed proofType,
+ address indexed verifierSetBy
+ );
+
+ /**
+ * @dev Thrown when l2 block timestamp is not correct
+ */
+ error BlockTimestampError();
+
+ /**
+ * @dev Thrown when the starting rootHash does not match the existing state
+ */
+ error StartingRootHashDoesNotMatch();
+
+ /**
+ * @dev Thrown when blockData is empty
+ */
+ error EmptyBlockDataArray();
+
+ /**
+ * @dev Thrown when block contains zero transactions
+ */
+ error EmptyBlock();
+
+ /**
+ * @dev Thrown when zk proof is empty bytes
+ */
+ error ProofIsEmpty();
+
+ /**
+ * @dev Thrown when zk proof type is invalid
+ */
+ error InvalidProofType();
+
+ /**
+ * @dev Thrown when zk proof is invalid
+ */
+ error InvalidProof();
+
+ /**
+ * @notice Adds or updated the verifier contract address for a proof type
+ * @dev DEFAULT_ADMIN_ROLE is required to execute
+ * @param _newVerifierAddress The address for the verifier contract
+ * @param _proofType The proof type being set/updated
+ **/
+ function setVerifierAddress(address _newVerifierAddress, uint256 _proofType) external;
+
+ /**
+ * @notice Finalizes blocks without using a proof
+ * @dev DEFAULT_ADMIN_ROLE is required to execute
+ * @param _calldata The full BlockData collection - block, transaction and log data
+ **/
+ function finalizeBlocksWithoutProof(BlockData[] calldata _calldata) external;
+
+ /**
+ * @notice Finalizes blocks without using a proof
+ * @dev OPERATOR_ROLE is required to execute
+ * @dev If the verifier based on proof type is not found, it defaults to the default verifier type
+ * @param _calldata The full BlockData collection - block, transaction and log data
+ * @param _proof The proof to verified with the proof type verifier contract
+ * @param _proofType The proof type to determine which verifier contract to use
+ * @param _parentStateRootHash The beginning roothash to start with
+ **/
+ function finalizeBlocks(
+ BlockData[] calldata _calldata,
+ bytes calldata _proof,
+ uint256 _proofType,
+ bytes32 _parentStateRootHash
+ ) external;
+}
diff --git a/contracts/contracts/messageService/MessageServiceBase.sol b/contracts/contracts/messageService/MessageServiceBase.sol
new file mode 100644
index 000000000..03e741ade
--- /dev/null
+++ b/contracts/contracts/messageService/MessageServiceBase.sol
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity 0.8.19;
+
+import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
+import { IMessageService } from "../interfaces/IMessageService.sol";
+
+/**
+ * @title Base contract to manage cross-chain messaging.
+ * @author ConsenSys Software Inc.
+ */
+abstract contract MessageServiceBase is Initializable {
+ IMessageService public messageService;
+ address public remoteSender;
+
+ uint256[10] private __base_gap;
+
+ /**
+ * @dev Thrown when the caller address is not the message service address
+ */
+ error CallerIsNotMessageService();
+
+ /**
+ * @dev Thrown when remote sender address is not authorized.
+ */
+ error SenderNotAuthorized();
+
+ /**
+ * @dev Thrown when an address is the default zero address.
+ */
+ error ZeroAddressNotAllowed();
+
+ /**
+ * @dev Modifier to make sure the caller is the known message service.
+ *
+ * Requirements:
+ *
+ * - The msg.sender must be the message service.
+ */
+ modifier onlyMessagingService() {
+ if (msg.sender != address(messageService)) {
+ revert CallerIsNotMessageService();
+ }
+ _;
+ }
+
+ /**
+ * @dev Modifier to make sure the original sender is allowed.
+ *
+ * Requirements:
+ *
+ * - The original message sender via the message service must be a known sender.
+ */
+ modifier onlyAuthorizedRemoteSender() {
+ if (messageService.sender() != remoteSender) {
+ revert SenderNotAuthorized();
+ }
+ _;
+ }
+
+ /**
+ * @notice Initializes the message service
+ * @dev Must be initialized in the initialize function of the main contract or constructor
+ * @param _messageService The message service address, cannot be empty.
+ **/
+ function __MessageServiceBase_init(address _messageService) internal onlyInitializing {
+ if (_messageService == address(0)) {
+ revert ZeroAddressNotAllowed();
+ }
+
+ messageService = IMessageService(_messageService);
+ }
+
+ /**
+ * @notice Sets the remote sender
+ * @param _remoteSender The authorized remote sender address, cannot be empty.
+ **/
+ function _setRemoteSender(address _remoteSender) internal {
+ if (_remoteSender == address(0)) {
+ revert ZeroAddressNotAllowed();
+ }
+
+ remoteSender = _remoteSender;
+ }
+}
diff --git a/contracts/contracts/messageService/l1/L1MessageManager.sol b/contracts/contracts/messageService/l1/L1MessageManager.sol
new file mode 100644
index 000000000..c98b561dc
--- /dev/null
+++ b/contracts/contracts/messageService/l1/L1MessageManager.sol
@@ -0,0 +1,99 @@
+// SPDX-License-Identifier: AGPL-3.0
+pragma solidity 0.8.19;
+
+import { IL1MessageManager } from "../../interfaces/IL1MessageManager.sol";
+
+/**
+ * @title Contract to manage cross-chain message hashes storage and status on L1.
+ * @author ConsenSys Software Inc.
+ */
+abstract contract L1MessageManager is IL1MessageManager {
+ uint8 public constant INBOX_STATUS_UNKNOWN = 0;
+ uint8 public constant INBOX_STATUS_RECEIVED = 1;
+
+ uint8 public constant OUTBOX_STATUS_UNKNOWN = 0;
+ uint8 public constant OUTBOX_STATUS_SENT = 1;
+ uint8 public constant OUTBOX_STATUS_RECEIVED = 2;
+
+ /// @dev There is a uint216 worth of storage layout here.
+
+ /// @dev Mapping to store L1->L2 message hashes status.
+ /// @dev messageHash => messageStatus (0: unknown, 1: sent, 2: received).
+ mapping(bytes32 => uint256) public outboxL1L2MessageStatus;
+
+ /// @dev Mapping to store L2->L1 message hashes status.
+ /// @dev messageHash => messageStatus (0: unknown, 1: received).
+ mapping(bytes32 => uint256) public inboxL2L1MessageStatus;
+
+ /// @dev Keep free storage slots for future implementation updates to avoid storage collision.
+ // *******************************************************************************************
+ // NB: THIS GAP HAS BEEN PUSHED OUT IN FAVOUR OF THE GAP INSIDE THE REENTRANCY CODE
+ //uint256[50] private __gap;
+ // NB: DO NOT USE THIS GAP
+ // *******************************************************************************************
+
+ /**
+ * @notice Add a cross-chain L2->L1 message hash in storage.
+ * @dev Once the event is emitted, it should be ready for claiming (post block finalization).
+ * @param _messageHash Hash of the message.
+ */
+ function _addL2L1MessageHash(bytes32 _messageHash) internal {
+ if (inboxL2L1MessageStatus[_messageHash] != INBOX_STATUS_UNKNOWN) {
+ revert MessageAlreadyReceived(_messageHash);
+ }
+
+ inboxL2L1MessageStatus[_messageHash] = INBOX_STATUS_RECEIVED;
+
+ emit L2L1MessageHashAddedToInbox(_messageHash);
+ }
+
+ /**
+ * @notice Update the status of L2->L1 message when a user claims a message on L1.
+ * @dev The L2->L1 message is removed from storage.
+ * @dev Due to the nature of the rollup, we should not get a second entry of this.
+ * @param _messageHash Hash of the message.
+ */
+ function _updateL2L1MessageStatusToClaimed(bytes32 _messageHash) internal {
+ if (inboxL2L1MessageStatus[_messageHash] != INBOX_STATUS_RECEIVED) {
+ revert MessageDoesNotExistOrHasAlreadyBeenClaimed();
+ }
+
+ delete inboxL2L1MessageStatus[_messageHash];
+ }
+
+ /**
+ * @notice Add L1->L2 message hash in storage when a message is sent on L1.
+ * @param _messageHash Hash of the message.
+ */
+ function _addL1L2MessageHash(bytes32 _messageHash) internal {
+ outboxL1L2MessageStatus[_messageHash] = OUTBOX_STATUS_SENT;
+ }
+
+ /**
+ * @notice Update the status of L1->L2 messages as received when messages has been stored on L2.
+ * @dev The expectation here is that the rollup is limited to 100 hashes being added here - array is not open ended.
+ * @param _messageHashes List of message hashes.
+ */
+ function _updateL1L2MessageStatusToReceived(bytes32[] memory _messageHashes) internal {
+ uint256 messageHashArrayLength = _messageHashes.length;
+
+ for (uint256 i; i < messageHashArrayLength; ) {
+ bytes32 messageHash = _messageHashes[i];
+ uint256 existingStatus = outboxL1L2MessageStatus[messageHash];
+
+ if (existingStatus == OUTBOX_STATUS_UNKNOWN) {
+ revert L1L2MessageNotSent(messageHash);
+ }
+
+ if (existingStatus != OUTBOX_STATUS_RECEIVED) {
+ outboxL1L2MessageStatus[messageHash] = OUTBOX_STATUS_RECEIVED;
+ }
+
+ unchecked {
+ i++;
+ }
+ }
+
+ emit L1L2MessagesReceivedOnL2(_messageHashes);
+ }
+}
diff --git a/contracts/contracts/messageService/l1/L1MessageService.sol b/contracts/contracts/messageService/l1/L1MessageService.sol
new file mode 100644
index 000000000..6f0054aff
--- /dev/null
+++ b/contracts/contracts/messageService/l1/L1MessageService.sol
@@ -0,0 +1,220 @@
+// SPDX-License-Identifier: AGPL-3.0
+pragma solidity 0.8.19;
+
+import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
+import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
+
+import { IMessageService } from "../../interfaces/IMessageService.sol";
+import { IGenericErrors } from "../../interfaces/IGenericErrors.sol";
+import { PauseManager } from "../lib/PauseManager.sol";
+import { RateLimiter } from "../lib/RateLimiter.sol";
+import { L1MessageManager } from "./L1MessageManager.sol";
+
+/**
+ * @title Contract to manage cross-chain messaging on L1.
+ * @author ConsenSys Software Inc.
+ */
+abstract contract L1MessageService is
+ Initializable,
+ RateLimiter,
+ L1MessageManager,
+ ReentrancyGuardUpgradeable,
+ PauseManager,
+ IMessageService,
+ IGenericErrors
+{
+ // @dev This is initialised to save user cost with existing slot.
+ uint256 public nextMessageNumber;
+
+ address private _messageSender;
+
+ // Keep free storage slots for future implementation updates to avoid storage collision.
+ uint256[50] private __gap;
+
+ // @dev adding these should not affect storage as they are constants and are store in bytecode
+ uint256 private constant REFUND_OVERHEAD_IN_GAS = 42000;
+
+ /**
+ * @notice Initialises underlying message service dependencies.
+ * @dev _messageSender is initialised to a non-zero value for gas efficiency on claiming.
+ * @param _limitManagerAddress The address owning the rate limiting management role.
+ * @param _pauseManagerAddress The address owning the pause management role.
+ * @param _rateLimitPeriod The period to rate limit against.
+ * @param _rateLimitAmount The limit allowed for withdrawing the period.
+ **/
+ function __MessageService_init(
+ address _limitManagerAddress,
+ address _pauseManagerAddress,
+ uint256 _rateLimitPeriod,
+ uint256 _rateLimitAmount
+ ) internal onlyInitializing {
+ if (_limitManagerAddress == address(0)) {
+ revert ZeroAddressNotAllowed();
+ }
+
+ if (_pauseManagerAddress == address(0)) {
+ revert ZeroAddressNotAllowed();
+ }
+
+ __ERC165_init();
+ __Context_init();
+ __AccessControl_init();
+ __RateLimiter_init(_rateLimitPeriod, _rateLimitAmount);
+
+ _grantRole(RATE_LIMIT_SETTER_ROLE, _limitManagerAddress);
+ _grantRole(PAUSE_MANAGER_ROLE, _pauseManagerAddress);
+
+ nextMessageNumber = 1;
+ _messageSender = address(123456789);
+ }
+
+ /**
+ * @notice Adds a message for sending cross-chain and emits MessageSent.
+ * @dev The message number is preset (nextMessageNumber) and only incremented at the end if successful for the next caller.
+ * @dev This function should be called with a msg.value = _value + _fee. The fee will be paid on the destination chain.
+ * @param _to The address the message is intended for.
+ * @param _fee The fee being paid for the message delivery.
+ * @param _calldata The calldata to pass to the recipient.
+ **/
+ function sendMessage(
+ address _to,
+ uint256 _fee,
+ bytes calldata _calldata
+ ) external payable whenTypeNotPaused(L1_L2_PAUSE_TYPE) whenTypeNotPaused(GENERAL_PAUSE_TYPE) {
+ if (_to == address(0)) {
+ revert ZeroAddressNotAllowed();
+ }
+
+ if (_fee > msg.value) {
+ revert ValueSentTooLow();
+ }
+
+ uint256 messageNumber = nextMessageNumber;
+ uint256 valueSent = msg.value - _fee;
+
+ bytes32 messageHash = keccak256(abi.encode(msg.sender, _to, _fee, valueSent, messageNumber, _calldata));
+
+ // @dev Status check and revert is in the message manager
+ _addL1L2MessageHash(messageHash);
+
+ nextMessageNumber++;
+
+ emit MessageSent(msg.sender, _to, _fee, valueSent, messageNumber, _calldata, messageHash);
+ }
+
+ /**
+ * @notice Claims and delivers a cross-chain message.
+ * @dev _feeRecipient can be set to address(0) to receive as msg.sender.
+ * @dev _messageSender is set temporarily when claiming and reset post. Used in sender().
+ * @dev _messageSender is reset to address(123456789) to be more gas efficient.
+ * @param _from The address of the original sender.
+ * @param _to The address the message is intended for.
+ * @param _fee The fee being paid for the message delivery.
+ * @param _value The value to be transferred to the destination address.
+ * @param _feeRecipient The recipient for the fee.
+ * @param _calldata The calldata to pass to the recipient.
+ * @param _nonce The unique auto generated nonce used when sending the message.
+ **/
+ function claimMessage(
+ address _from,
+ address _to,
+ uint256 _fee,
+ uint256 _value,
+ address payable _feeRecipient,
+ bytes calldata _calldata,
+ uint256 _nonce
+ ) external nonReentrant distributeFees(_fee, _to, _calldata, _feeRecipient) {
+ _requireTypeNotPaused(L2_L1_PAUSE_TYPE);
+ _requireTypeNotPaused(GENERAL_PAUSE_TYPE);
+
+ bytes32 messageHash = keccak256(abi.encode(_from, _to, _fee, _value, _nonce, _calldata));
+
+ // @dev Status check and revert is in the message manager.
+ _updateL2L1MessageStatusToClaimed(messageHash);
+
+ _addUsedAmount(_fee + _value);
+
+ _messageSender = _from;
+
+ (bool callSuccess, bytes memory returnData) = _to.call{ value: _value }(_calldata);
+ if (!callSuccess) {
+ if (returnData.length > 0) {
+ assembly {
+ let data_size := mload(returnData)
+ revert(add(32, returnData), data_size)
+ }
+ } else {
+ revert MessageSendingFailed(_to);
+ }
+ }
+
+ _messageSender = address(123456789);
+
+ emit MessageClaimed(messageHash);
+ }
+
+ /**
+ * @notice Claims and delivers a cross-chain message.
+ * @dev _messageSender is set temporarily when claiming.
+ **/
+ function sender() external view returns (address) {
+ return _messageSender;
+ }
+
+ /**
+ * @notice Function to receive funds for liquidity purposes.
+ **/
+ receive() external payable virtual {}
+
+ /**
+ * @notice The unspent fee is refunded if applicable.
+ * @param _feeInWei The fee paid for delivery in Wei.
+ * @param _to The recipient of the message and gas refund.
+ * @param _calldata The calldata of the message.
+ **/
+ modifier distributeFees(
+ uint256 _feeInWei,
+ address _to,
+ bytes calldata _calldata,
+ address _feeRecipient
+ ) {
+ //pre-execution
+ uint256 startingGas = gasleft();
+ _;
+ //post-execution
+
+ // we have a fee
+ if (_feeInWei > 0) {
+ // default postman fee
+ uint256 deliveryFee = _feeInWei;
+
+ // do we have empty calldata?
+ if (_calldata.length == 0) {
+ bool isDestinationEOA;
+
+ assembly {
+ isDestinationEOA := iszero(extcodesize(_to))
+ }
+
+ // are we calling an EOA
+ if (isDestinationEOA) {
+ // initial + cost to call and refund minus gasleft
+ deliveryFee = (startingGas + REFUND_OVERHEAD_IN_GAS - gasleft()) * tx.gasprice;
+
+ if (_feeInWei > deliveryFee) {
+ payable(_to).send(_feeInWei - deliveryFee);
+ } else {
+ deliveryFee = _feeInWei;
+ }
+ }
+ }
+
+ address feeReceiver = _feeRecipient == address(0) ? msg.sender : _feeRecipient;
+
+ bool callSuccess = payable(feeReceiver).send(deliveryFee);
+ if (!callSuccess) {
+ revert FeePaymentFailed(feeReceiver);
+ }
+ }
+ }
+}
diff --git a/contracts/contracts/messageService/l2/L2MessageManager.sol b/contracts/contracts/messageService/l2/L2MessageManager.sol
new file mode 100644
index 000000000..91ab3f3b1
--- /dev/null
+++ b/contracts/contracts/messageService/l2/L2MessageManager.sol
@@ -0,0 +1,78 @@
+// SPDX-License-Identifier: AGPL-3.0
+pragma solidity 0.8.19;
+
+import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
+import { IL2MessageManager } from "../../interfaces/IL2MessageManager.sol";
+import { PauseManager } from "../lib/PauseManager.sol";
+
+/**
+ * @title Contract to manage cross-chain message hashes storage and statuses on L2.
+ * @author ConsenSys Software Inc.
+ */
+abstract contract L2MessageManager is Initializable, PauseManager, IL2MessageManager {
+ uint8 public constant INBOX_STATUS_UNKNOWN = 0;
+ uint8 public constant INBOX_STATUS_RECEIVED = 1;
+ uint8 public constant INBOX_STATUS_CLAIMED = 2;
+
+ /// @dev There is a uint232 worth of storage layout here
+
+ bytes32 public constant L1_L2_MESSAGE_SETTER_ROLE = keccak256("L1_L2_MESSAGE_SETTER_ROLE");
+
+ /**
+ * @dev Mapping to store L1->L2 message hashes status.
+ * @dev messageHash => messageStatus (0: unknown, 1: received, 2: claimed).
+ */
+ mapping(bytes32 => uint256) public inboxL1L2MessageStatus;
+
+ /// @dev Keep free storage slots for future implementation updates to avoid storage collision.
+ // *******************************************************************************************
+ // NB: THIS GAP HAS BEEN PUSHED OUT IN FAVOUR OF THE GAP INSIDE THE REENTRANCY CODE
+ //uint256[50] private __gap;
+ // NB: DO NOT USE THIS GAP
+ // *******************************************************************************************
+
+ /**
+ * @notice Initialises L2 message manager contract.
+ * @param _l1l2MessageSetter The address owning the L1_L2_MESSAGE_SETTER_ROLE role.
+ **/
+ function __L2MessageManager_init(address _l1l2MessageSetter) internal onlyInitializing {
+ _grantRole(L1_L2_MESSAGE_SETTER_ROLE, _l1l2MessageSetter);
+ }
+
+ /**
+ * @notice Add a cross-chain L1->L2 message hashes in storage.
+ * @dev Only address that has the role 'L1_L2_MESSAGE_SETTER_ROLE' are allowed to call this function.
+ * @param _messageHashes Message hashes array.
+ */
+ function addL1L2MessageHashes(bytes32[] calldata _messageHashes) external onlyRole(L1_L2_MESSAGE_SETTER_ROLE) {
+ uint256 messageHashesLength = _messageHashes.length;
+
+ if (messageHashesLength > 100) {
+ revert MessageHashesListLengthHigherThanOneHundred(messageHashesLength);
+ }
+
+ for (uint256 i; i < messageHashesLength; ) {
+ bytes32 messageHash = _messageHashes[i];
+ if (inboxL1L2MessageStatus[messageHash] == INBOX_STATUS_UNKNOWN) {
+ inboxL1L2MessageStatus[messageHash] = INBOX_STATUS_RECEIVED;
+ }
+ unchecked {
+ i++;
+ }
+ }
+
+ emit L1L2MessageHashesAddedToInbox(_messageHashes);
+ }
+
+ /**
+ * @notice Update the status of L1->L2 message when a user claim a message on L2.
+ * @param _messageHash Hash of the message.
+ */
+ function _updateL1L2MessageStatusToClaimed(bytes32 _messageHash) internal {
+ if (inboxL1L2MessageStatus[_messageHash] != INBOX_STATUS_RECEIVED) {
+ revert MessageDoesNotExistOrHasAlreadyBeenClaimed();
+ }
+
+ inboxL1L2MessageStatus[_messageHash] = INBOX_STATUS_CLAIMED;
+ }
+}
diff --git a/contracts/contracts/messageService/l2/L2MessageService.sol b/contracts/contracts/messageService/l2/L2MessageService.sol
new file mode 100644
index 000000000..4b3af84d9
--- /dev/null
+++ b/contracts/contracts/messageService/l2/L2MessageService.sol
@@ -0,0 +1,250 @@
+// SPDX-License-Identifier: AGPL-3.0
+pragma solidity 0.8.19;
+
+import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
+import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
+import { CodecV2 } from "../lib/Codec.sol";
+import { IMessageService } from "../../interfaces/IMessageService.sol";
+import { IGenericErrors } from "../../interfaces/IGenericErrors.sol";
+import { RateLimiter } from "../lib/RateLimiter.sol";
+import { L2MessageManager } from "./L2MessageManager.sol";
+
+/**
+ * @title Contract to manage cross-chain messaging on L2.
+ * @author ConsenSys Software Inc.
+ */
+contract L2MessageService is
+ Initializable,
+ RateLimiter,
+ L2MessageManager,
+ ReentrancyGuardUpgradeable,
+ IMessageService,
+ IGenericErrors
+{
+ // Keep free storage slots for future implementation updates to avoid storage collision.
+ uint256[50] private __gap_L2MessageService;
+
+ bytes32 public constant MINIMUM_FEE_SETTER_ROLE = keccak256("MINIMUM_FEE_SETTER_ROLE");
+
+ address private _messageSender;
+
+ // @dev initialise to save user cost with existing slot.
+ uint256 public nextMessageNumber;
+
+ // @dev initialise minimumFeeInWei variable.
+ uint256 public minimumFeeInWei;
+
+ // @dev adding these should not affect storage as they are constants and are store in bytecode
+ uint256 private constant REFUND_OVERHEAD_IN_GAS = 47500;
+
+ /// @custom:oz-upgrades-unsafe-allow constructor
+ constructor() {
+ _disableInitializers();
+ }
+
+ /**
+ * @notice Initialises underlying message service dependencies.
+ * @param _securityCouncil The address owning the security council role.
+ * @param _l1l2MessageSetter The address owning the add L1L2MessageHashes functionality.
+ * @param _rateLimitPeriod The period to rate limit against.
+ * @param _rateLimitAmount The limit allowed for withdrawing the period.
+ **/
+ function initialize(
+ address _securityCouncil,
+ address _l1l2MessageSetter,
+ uint256 _rateLimitPeriod,
+ uint256 _rateLimitAmount
+ ) public initializer {
+ if (_securityCouncil == address(0)) {
+ revert ZeroAddressNotAllowed();
+ }
+
+ if (_l1l2MessageSetter == address(0)) {
+ revert ZeroAddressNotAllowed();
+ }
+
+ __ERC165_init();
+ __Context_init();
+ __AccessControl_init();
+ __RateLimiter_init(_rateLimitPeriod, _rateLimitAmount);
+ __L2MessageManager_init(_l1l2MessageSetter);
+
+ nextMessageNumber = 1;
+
+ _grantRole(DEFAULT_ADMIN_ROLE, _securityCouncil);
+ _grantRole(MINIMUM_FEE_SETTER_ROLE, _securityCouncil);
+ _grantRole(RATE_LIMIT_SETTER_ROLE, _securityCouncil);
+ _grantRole(PAUSE_MANAGER_ROLE, _securityCouncil);
+
+ _messageSender = address(123456789);
+ }
+
+ /**
+ * @notice Adds a message for sending cross-chain and emits a relevant event.
+ * @dev The message number is preset and only incremented at the end if successful for the next caller.
+ * @param _to The address the message is intended for.
+ * @param _fee The fee being paid for the message delivery.
+ * @param _calldata The calldata to pass to the recipient.
+ **/
+ function sendMessage(address _to, uint256 _fee, bytes calldata _calldata) external payable {
+ _requireTypeNotPaused(L2_L1_PAUSE_TYPE);
+ _requireTypeNotPaused(GENERAL_PAUSE_TYPE);
+
+ if (_to == address(0)) {
+ revert ZeroAddressNotAllowed();
+ }
+
+ if (_fee > msg.value) {
+ revert ValueSentTooLow();
+ }
+
+ uint256 coinbaseFee = minimumFeeInWei;
+
+ if (_fee < coinbaseFee) {
+ revert FeeTooLow();
+ }
+
+ uint256 postmanFee;
+ uint256 valueSent;
+
+ unchecked {
+ postmanFee = _fee - coinbaseFee;
+ valueSent = msg.value - _fee;
+ }
+
+ uint256 messageNumber = nextMessageNumber;
+ /// @dev Rate limit and revert is in the rate limiter.
+ _addUsedAmount(valueSent + postmanFee);
+
+ bytes32 messageHash = keccak256(abi.encode(msg.sender, _to, postmanFee, valueSent, messageNumber, _calldata));
+
+ nextMessageNumber++;
+
+ (bool success, ) = block.coinbase.call{ value: coinbaseFee }("");
+ if (!success) {
+ revert FeePaymentFailed(block.coinbase);
+ }
+
+ emit MessageSent(msg.sender, _to, postmanFee, valueSent, messageNumber, _calldata, messageHash);
+ }
+
+ /**
+ * @notice Claims and delivers a cross-chain message.
+ * @dev _feeRecipient Can be set to address(0) to receive as msg.sender.
+ * @dev messageSender Is set temporarily when claiming and reset post.
+ * @param _from The address of the original sender.
+ * @param _to The address the message is intended for.
+ * @param _fee The fee being paid for the message delivery.
+ * @param _value The value to be transferred to the destination address.
+ * @param _feeRecipient The recipient for the fee.
+ * @param _calldata The calldata to pass to the recipient.
+ * @param _nonce The unique auto generated message number used when sending the message.
+ **/
+ function claimMessage(
+ address _from,
+ address _to,
+ uint256 _fee,
+ uint256 _value,
+ address payable _feeRecipient,
+ bytes calldata _calldata,
+ uint256 _nonce
+ ) external nonReentrant distributeFees(_fee, _to, _calldata, _feeRecipient) {
+ _requireTypeNotPaused(L1_L2_PAUSE_TYPE);
+ _requireTypeNotPaused(GENERAL_PAUSE_TYPE);
+
+ bytes32 messageHash = keccak256(abi.encode(_from, _to, _fee, _value, _nonce, _calldata));
+
+ /// @dev Status check and revert is in the message manager.
+ _updateL1L2MessageStatusToClaimed(messageHash);
+
+ _messageSender = _from;
+
+ (bool callSuccess, bytes memory returnData) = _to.call{ value: _value }(_calldata);
+ if (!callSuccess) {
+ if (returnData.length > 0) {
+ assembly {
+ let data_size := mload(returnData)
+ revert(add(32, returnData), data_size)
+ }
+ } else {
+ revert MessageSendingFailed(_to);
+ }
+ }
+
+ _messageSender = address(123456789);
+ emit MessageClaimed(messageHash);
+ }
+
+ /**
+ * @notice The Fee Manager sets a minimum fee to address DOS protection.
+ * @param _feeInWei New minimum fee in Wei.
+ **/
+ function setMinimumFee(uint256 _feeInWei) external onlyRole(MINIMUM_FEE_SETTER_ROLE) {
+ minimumFeeInWei = _feeInWei;
+ }
+
+ /**
+ * @dev The _messageSender address is set temporarily when claiming.
+ * @return _messageSender address.
+ **/
+ function sender() external view returns (address) {
+ return _messageSender;
+ }
+
+ /**
+ * @notice Function to receive funds for liquidity purposes.
+ **/
+ receive() external payable virtual {}
+
+ /**
+ * @notice The unspent fee is refunded if applicable.
+ * @param _feeInWei The fee paid for delivery in Wei.
+ * @param _to The recipient of the message and gas refund.
+ * @param _calldata The calldata of the message.
+ **/
+ modifier distributeFees(
+ uint256 _feeInWei,
+ address _to,
+ bytes calldata _calldata,
+ address _feeRecipient
+ ) {
+ //pre-execution
+ uint256 startingGas = gasleft();
+ _;
+ //post-execution
+
+ // we have a fee
+ if (_feeInWei > 0) {
+ // default postman fee
+ uint256 deliveryFee = _feeInWei;
+
+ // do we have empty calldata?
+ if (_calldata.length == 0) {
+ bool isDestinationEOA;
+
+ assembly {
+ isDestinationEOA := iszero(extcodesize(_to))
+ }
+
+ // are we calling an EOA
+ if (isDestinationEOA) {
+ // initial + cost to call and refund minus gasleft
+ deliveryFee = (startingGas + REFUND_OVERHEAD_IN_GAS - gasleft()) * tx.gasprice;
+
+ if (_feeInWei > deliveryFee) {
+ payable(_to).send(_feeInWei - deliveryFee);
+ } else {
+ deliveryFee = _feeInWei;
+ }
+ }
+ }
+
+ address feeReceiver = _feeRecipient == address(0) ? msg.sender : _feeRecipient;
+
+ bool callSuccess = payable(feeReceiver).send(deliveryFee);
+ if (!callSuccess) {
+ revert FeePaymentFailed(feeReceiver);
+ }
+ }
+ }
+}
diff --git a/contracts/contracts/messageService/lib/Codec.sol b/contracts/contracts/messageService/lib/Codec.sol
new file mode 100644
index 000000000..cecc444ca
--- /dev/null
+++ b/contracts/contracts/messageService/lib/Codec.sol
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: AGPL-3.0
+pragma solidity 0.8.19;
+
+/**
+ * @title Decoding functions for message service anchoring and bytes slicing.
+ * @author ConsenSys Software Inc.
+ * @notice You can use this to slice bytes and extract anchoring hashes from calldata.
+ **/
+library CodecV2 {
+ /**
+ * @notice Decodes a collection of bytes32 (hashes) from the calldata of a transaction.
+ * @dev Extracts and decodes skipping the function selector (selector is expected in the input).
+ * @dev A check beforehand must be performed to confirm this is the correct type of transaction.
+ * @param _calldataWithSelector The calldata for the transaction.
+ * @return bytes32[] - array of message hashes.
+ **/
+ function _extractXDomainAddHashes(bytes memory _calldataWithSelector) internal pure returns (bytes32[] memory) {
+ assembly {
+ let len := sub(mload(_calldataWithSelector), 4)
+ _calldataWithSelector := add(_calldataWithSelector, 0x4)
+ mstore(_calldataWithSelector, len)
+ }
+
+ return abi.decode(_calldataWithSelector, (bytes32[]));
+ }
+}
diff --git a/contracts/contracts/messageService/lib/PauseManager.sol b/contracts/contracts/messageService/lib/PauseManager.sol
new file mode 100644
index 000000000..895480f87
--- /dev/null
+++ b/contracts/contracts/messageService/lib/PauseManager.sol
@@ -0,0 +1,88 @@
+// SPDX-License-Identifier: AGPL-3.0
+
+pragma solidity 0.8.19;
+
+import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
+import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
+import { IPauseManager } from "../../interfaces/IPauseManager.sol";
+
+/**
+ * @title Contract to manage cross-chain function pausing.
+ * @author ConsenSys Software Inc.
+ */
+abstract contract PauseManager is Initializable, IPauseManager, AccessControlUpgradeable {
+ bytes32 public constant PAUSE_MANAGER_ROLE = keccak256("PAUSE_MANAGER_ROLE");
+
+ bytes32 public constant GENERAL_PAUSE_TYPE = keccak256("GENERAL_PAUSE_TYPE");
+ bytes32 public constant L1_L2_PAUSE_TYPE = keccak256("L1_L2_PAUSE_TYPE");
+ bytes32 public constant L2_L1_PAUSE_TYPE = keccak256("L2_L1_PAUSE_TYPE");
+ bytes32 public constant PROVING_SYSTEM_PAUSE_TYPE = keccak256("PROVING_SYSTEM_PAUSE_TYPE");
+
+ mapping(bytes32 => bool) public pauseTypeStatuses;
+
+ uint256[10] private _gap;
+
+ /**
+ * @dev Modifier to make a function callable only when the type is not paused.
+ *
+ * Requirements:
+ *
+ * - The type must not be paused.
+ */
+ modifier whenTypeNotPaused(bytes32 _pauseType) {
+ _requireTypeNotPaused(_pauseType);
+ _;
+ }
+
+ /**
+ * @dev Modifier to make a function callable only when the type is paused.
+ *
+ * Requirements:
+ *
+ * - The type must not be paused.
+ */
+ modifier whenTypePaused(bytes32 _pauseType) {
+ _requireTypePaused(_pauseType);
+ _;
+ }
+
+ /**
+ * @dev Throws if the type is not paused.
+ * @param _pauseType The keccak256 pause type being checked,
+ */
+ function _requireTypePaused(bytes32 _pauseType) internal view virtual {
+ if (!pauseTypeStatuses[_pauseType]) {
+ revert IsNotPaused(_pauseType);
+ }
+ }
+
+ /**
+ * @dev Throws if the type is paused.
+ * @param _pauseType The keccak256 pause type being checked,
+ */
+ function _requireTypeNotPaused(bytes32 _pauseType) internal view virtual {
+ if (pauseTypeStatuses[_pauseType]) {
+ revert IsPaused(_pauseType);
+ }
+ }
+
+ /**
+ * @notice Pauses functionality by specific type.
+ * @dev Requires PAUSE_MANAGER_ROLE.
+ * @param _pauseType keccak256 pause type.
+ **/
+ function pauseByType(bytes32 _pauseType) external whenTypeNotPaused(_pauseType) onlyRole(PAUSE_MANAGER_ROLE) {
+ pauseTypeStatuses[_pauseType] = true;
+ emit Paused(_msgSender(), _pauseType);
+ }
+
+ /**
+ * @notice Unpauses functionality by specific type.
+ * @dev Requires PAUSE_MANAGER_ROLE.
+ * @param _pauseType keccak256 pause type.
+ **/
+ function unPauseByType(bytes32 _pauseType) external whenTypePaused(_pauseType) onlyRole(PAUSE_MANAGER_ROLE) {
+ pauseTypeStatuses[_pauseType] = false;
+ emit UnPaused(_msgSender(), _pauseType);
+ }
+}
diff --git a/contracts/contracts/messageService/lib/RateLimiter.sol b/contracts/contracts/messageService/lib/RateLimiter.sol
new file mode 100644
index 000000000..84d8167ea
--- /dev/null
+++ b/contracts/contracts/messageService/lib/RateLimiter.sol
@@ -0,0 +1,113 @@
+// SPDX-License-Identifier: AGPL-3.0
+pragma solidity 0.8.19;
+
+import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
+import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
+import { IRateLimiter } from "../../interfaces/IRateLimiter.sol";
+
+/**
+ * @title Rate Limiter by period and amount using the block timestamp.
+ * @author ConsenSys Software Inc.
+ * @notice You can use this control numeric limits over a period using timestamp.
+ **/
+contract RateLimiter is Initializable, IRateLimiter, AccessControlUpgradeable {
+ bytes32 public constant RATE_LIMIT_SETTER_ROLE = keccak256("RATE_LIMIT_SETTER_ROLE");
+
+ uint256 public periodInSeconds; // how much time before limit resets.
+ uint256 public limitInWei; // max ether to withdraw per period.
+
+ // @dev Public for ease of consumption.
+ // @notice The time at which the current period ends at.
+ uint256 public currentPeriodEnd;
+
+ // @dev Public for ease of consumption.
+ // @notice Amounts already withdrawn this period.
+ uint256 public currentPeriodAmountInWei;
+
+ uint256[10] private _gap;
+
+ /**
+ * @notice Initialises the limits and period for the rate limiter.
+ * @param _periodInSeconds The length of the period in seconds.
+ * @param _limitInWei The limit allowed in the period in Wei.
+ **/
+ function __RateLimiter_init(uint256 _periodInSeconds, uint256 _limitInWei) internal onlyInitializing {
+ if (_periodInSeconds == 0) {
+ revert PeriodIsZero();
+ }
+
+ if (_limitInWei == 0) {
+ revert LimitIsZero();
+ }
+
+ periodInSeconds = _periodInSeconds;
+ limitInWei = _limitInWei;
+ currentPeriodEnd = block.timestamp + _periodInSeconds;
+ }
+
+ /**
+ * @notice Increments the amount used in the period.
+ * @dev The amount determining logic is external to this (e.g. fees are included when calling here).
+ * @dev Reverts if the limit is breached.
+ * @param _usedAmount The amount used to be added.
+ **/
+ function _addUsedAmount(uint256 _usedAmount) internal {
+ uint256 currentPeriodAmountTemp;
+
+ if (currentPeriodEnd < block.timestamp) {
+ currentPeriodEnd = block.timestamp + periodInSeconds;
+ currentPeriodAmountTemp = _usedAmount;
+ } else {
+ currentPeriodAmountTemp = currentPeriodAmountInWei + _usedAmount;
+ }
+
+ if (currentPeriodAmountTemp > limitInWei) {
+ revert RateLimitExceeded();
+ }
+
+ currentPeriodAmountInWei = currentPeriodAmountTemp;
+ }
+
+ /**
+ * @notice Resets the rate limit amount.
+ * @dev If the used amount is higher, it is set to the limit to avoid confusion/issues.
+ * @dev Only the RATE_LIMIT_SETTER_ROLE is allowed to execute this function.
+ * @dev Emits the LimitAmountChanged event.
+ * @dev usedLimitAmountToSet will use the default value of zero if period has expired
+ * @param _amount The amount to reset the limit to.
+ **/
+ function resetRateLimitAmount(uint256 _amount) external onlyRole(RATE_LIMIT_SETTER_ROLE) {
+ uint256 usedLimitAmountToSet;
+ bool amountUsedLoweredToLimit;
+ bool usedAmountResetToZero;
+
+ if (currentPeriodEnd < block.timestamp) {
+ currentPeriodEnd = block.timestamp + periodInSeconds;
+ usedAmountResetToZero = true;
+ } else {
+ if (_amount < currentPeriodAmountInWei) {
+ usedLimitAmountToSet = _amount;
+ amountUsedLoweredToLimit = true;
+ }
+ }
+
+ limitInWei = _amount;
+
+ if (usedAmountResetToZero || amountUsedLoweredToLimit) {
+ currentPeriodAmountInWei = usedLimitAmountToSet;
+ }
+
+ emit LimitAmountChanged(_msgSender(), _amount, amountUsedLoweredToLimit, usedAmountResetToZero);
+ }
+
+ /**
+ * @notice Resets the amount used to zero.
+ * @dev Only the RATE_LIMIT_SETTER_ROLE is allowed to execute this function.
+ * @dev Emits the AmountUsedInPeriodReset event.
+ **/
+ function resetAmountUsedInPeriod() external onlyRole(RATE_LIMIT_SETTER_ROLE) {
+ currentPeriodAmountInWei = 0;
+
+ emit AmountUsedInPeriodReset(_msgSender());
+ }
+}
diff --git a/contracts/contracts/messageService/lib/Rlp.sol b/contracts/contracts/messageService/lib/Rlp.sol
new file mode 100644
index 000000000..7483502b4
--- /dev/null
+++ b/contracts/contracts/messageService/lib/Rlp.sol
@@ -0,0 +1,318 @@
+// SPDX-License-Identifier: Apache-2.0
+
+/**
+ * @author Hamdi Allam hamdi.allam97@gmail.com
+ * @notice Please reach out with any questions or concerns.
+ */
+pragma solidity 0.8.19;
+
+error NotList();
+error WrongBytesLength();
+error NoNext();
+error MemoryOutOfBounds(uint256 inde);
+
+library RLPReader {
+ uint8 internal constant STRING_SHORT_START = 0x80;
+ uint8 internal constant STRING_LONG_START = 0xb8;
+ uint8 internal constant LIST_SHORT_START = 0xc0;
+ uint8 internal constant LIST_LONG_START = 0xf8;
+ uint8 internal constant LIST_SHORT_START_MAX = 0xf7;
+ uint8 internal constant WORD_SIZE = 32;
+
+ struct RLPItem {
+ uint256 len;
+ uint256 memPtr;
+ }
+
+ struct Iterator {
+ RLPItem item; // Item that's being iterated over.
+ uint256 nextPtr; // Position of the next item in the list.
+ }
+
+ /**
+ * @dev Returns the next element in the iteration. Reverts if it has no next element.
+ * @param _self The iterator.
+ * @return nextItem The next element in the iteration.
+ */
+ function _next(Iterator memory _self) internal pure returns (RLPItem memory nextItem) {
+ if (!_hasNext(_self)) {
+ revert NoNext();
+ }
+
+ uint256 ptr = _self.nextPtr;
+ uint256 itemLength = _itemLength(ptr);
+ _self.nextPtr = ptr + itemLength;
+
+ nextItem.len = itemLength;
+ nextItem.memPtr = ptr;
+ }
+
+ /**
+ * @dev Returns the number 'skiptoNum' element in the iteration.
+ * @param _self The iterator.
+ * @param _skipToNum Element position in the RLP item iterator to return.
+ * @return item The number 'skipToNum' element in the iteration.
+ */
+ function _skipTo(Iterator memory _self, uint256 _skipToNum) internal pure returns (RLPItem memory item) {
+ uint256 lenX;
+ uint256 memPtrStart = _self.item.memPtr;
+ uint256 endPtr;
+ uint256 byte0;
+ uint256 byteLen;
+
+ assembly {
+ // get first byte to know if it is a short/long list
+ byte0 := byte(0, mload(memPtrStart))
+
+ // yul has no if/else so if it a short list ( < long list start )
+ switch lt(byte0, LIST_LONG_START)
+ case 1 {
+ // the length is just the difference in bytes
+ lenX := sub(byte0, 0xc0)
+ }
+ case 0 {
+ // at this point we care only about lists, so this is the default
+ // get how many next bytes indicate the list length
+ byteLen := sub(byte0, 0xf7)
+
+ // move one over to the list length start
+ memPtrStart := add(memPtrStart, 1)
+
+ // shift over grabbing the bytelen elements
+ lenX := div(mload(memPtrStart), exp(256, sub(32, byteLen)))
+ }
+
+ // get the end
+ endPtr := add(memPtrStart, lenX)
+ }
+
+ uint256 ptr = _self.nextPtr;
+ uint256 itemLength = _itemLength(ptr);
+ _self.nextPtr = ptr + itemLength;
+
+ for (uint256 i; i < _skipToNum - 1; ) {
+ ptr = _self.nextPtr;
+ if (ptr > endPtr) revert MemoryOutOfBounds(endPtr);
+ itemLength = _itemLength(ptr);
+ _self.nextPtr = ptr + itemLength;
+
+ unchecked {
+ i++;
+ }
+ }
+
+ item.len = itemLength;
+ item.memPtr = ptr;
+ }
+
+ /**
+ * @dev Returns true if the iteration has more elements.
+ * @param _self The iterator.
+ * @return True if the iteration has more elements.
+ */
+ function _hasNext(Iterator memory _self) internal pure returns (bool) {
+ RLPItem memory item = _self.item;
+ return _self.nextPtr < item.memPtr + item.len;
+ }
+
+ /**
+ * @param item RLP encoded bytes.
+ * @return newItem The RLP item.
+ */
+ function _toRlpItem(bytes memory item) internal pure returns (RLPItem memory newItem) {
+ uint256 memPtr;
+
+ assembly {
+ memPtr := add(item, 0x20)
+ }
+
+ newItem.len = item.length;
+ newItem.memPtr = memPtr;
+ }
+
+ /**
+ * @dev Creates an iterator. Reverts if item is not a list.
+ * @param _self The RLP item.
+ * @return iterator 'Iterator' over the item.
+ */
+ function _iterator(RLPItem memory _self) internal pure returns (Iterator memory iterator) {
+ if (!_isList(_self)) {
+ revert NotList();
+ }
+
+ uint256 ptr = _self.memPtr + _payloadOffset(_self.memPtr);
+ iterator.item = _self;
+ iterator.nextPtr = ptr;
+ }
+
+ /**
+ * @param _item The RLP item.
+ * @return (memPtr, len) Tuple: Location of the item's payload in memory.
+ */
+ function _payloadLocation(RLPItem memory _item) internal pure returns (uint256, uint256) {
+ uint256 offset = _payloadOffset(_item.memPtr);
+ uint256 memPtr = _item.memPtr + offset;
+ uint256 len = _item.len - offset; // data length
+ return (memPtr, len);
+ }
+
+ /**
+ * @param _item The RLP item.
+ * @return Indicator whether encoded payload is a list.
+ */
+ function _isList(RLPItem memory _item) internal pure returns (bool) {
+ if (_item.len == 0) return false;
+
+ uint8 byte0;
+ uint256 memPtr = _item.memPtr;
+ assembly {
+ byte0 := byte(0, mload(memPtr))
+ }
+
+ if (byte0 < LIST_SHORT_START) return false;
+ return true;
+ }
+
+ /**
+ * @param _item The RLP item.
+ * @return result Returns the item as an address.
+ */
+ function _toAddress(RLPItem memory _item) internal pure returns (address) {
+ // 1 byte for the length prefix
+ if (_item.len != 21) {
+ revert WrongBytesLength();
+ }
+
+ return address(uint160(_toUint(_item)));
+ }
+
+ /**
+ * @param _item The RLP item.
+ * @return result Returns the item as a uint256.
+ */
+ function _toUint(RLPItem memory _item) internal pure returns (uint256 result) {
+ if (_item.len == 0 || _item.len > 33) {
+ revert WrongBytesLength();
+ }
+
+ (uint256 memPtr, uint256 len) = _payloadLocation(_item);
+
+ assembly {
+ result := mload(memPtr)
+
+ // Shfit to the correct location if neccesary.
+ if lt(len, 32) {
+ result := div(result, exp(256, sub(32, len)))
+ }
+ }
+ }
+
+ /**
+ * @param _item The RLP item.
+ * @return result Returns the item as bytes.
+ */
+ function _toBytes(RLPItem memory _item) internal pure returns (bytes memory result) {
+ if (_item.len == 0) {
+ revert WrongBytesLength();
+ }
+
+ (uint256 memPtr, uint256 len) = _payloadLocation(_item);
+ result = new bytes(len);
+
+ uint256 destPtr;
+ assembly {
+ destPtr := add(0x20, result)
+ }
+
+ _copy(memPtr, destPtr, len);
+ }
+
+ /*
+ * Private Helpers
+ */
+
+ /**
+ * @param _memPtr Item memory pointer.
+ * @return Entire RLP item byte length.
+ */
+ function _itemLength(uint256 _memPtr) private pure returns (uint256) {
+ uint256 itemLen;
+ uint256 dataLen;
+ uint256 byte0;
+ assembly {
+ byte0 := byte(0, mload(_memPtr))
+ }
+
+ if (byte0 < STRING_SHORT_START) itemLen = 1;
+ else if (byte0 < STRING_LONG_START) itemLen = byte0 - STRING_SHORT_START + 1;
+ else if (byte0 < LIST_SHORT_START) {
+ assembly {
+ let byteLen := sub(byte0, 0xb7) // # Of bytes the actual length is.
+ _memPtr := add(_memPtr, 1) // Skip over the first byte.
+
+ /* 32 byte word size */
+ dataLen := div(mload(_memPtr), exp(256, sub(32, byteLen))) // Right shifting to get the len.
+ itemLen := add(dataLen, add(byteLen, 1))
+ }
+ } else if (byte0 < LIST_LONG_START) {
+ itemLen = byte0 - LIST_SHORT_START + 1;
+ } else {
+ assembly {
+ let byteLen := sub(byte0, 0xf7)
+ _memPtr := add(_memPtr, 1)
+
+ dataLen := div(mload(_memPtr), exp(256, sub(32, byteLen))) // Right shifting to the correct length.
+ itemLen := add(dataLen, add(byteLen, 1))
+ }
+ }
+
+ return itemLen;
+ }
+
+ /**
+ * @param _memPtr Item memory pointer.
+ * @return Number of bytes until the data.
+ */
+ function _payloadOffset(uint256 _memPtr) private pure returns (uint256) {
+ uint256 byte0;
+ assembly {
+ byte0 := byte(0, mload(_memPtr))
+ }
+
+ if (byte0 < STRING_SHORT_START) return 0;
+ else if (byte0 < STRING_LONG_START || (byte0 >= LIST_SHORT_START && byte0 < LIST_LONG_START)) return 1;
+ else if (byte0 < LIST_SHORT_START)
+ // being explicit
+ return byte0 - (STRING_LONG_START - 1) + 1;
+ else return byte0 - (LIST_LONG_START - 1) + 1;
+ }
+
+ /**
+ * @param _src Pointer to source.
+ * @param _dest Pointer to destination.
+ * @param _len Amount of memory to copy from the source.
+ */
+ function _copy(uint256 _src, uint256 _dest, uint256 _len) private pure {
+ if (_len == 0) return;
+
+ // copy as many word sizes as possible
+ for (; _len >= WORD_SIZE; _len -= WORD_SIZE) {
+ assembly {
+ mstore(_dest, mload(_src))
+ }
+
+ _src += WORD_SIZE;
+ _dest += WORD_SIZE;
+ }
+
+ if (_len > 0) {
+ // Left over bytes. Mask is used to remove unwanted bytes from the word.
+ uint256 mask = 256 ** (WORD_SIZE - _len) - 1;
+ assembly {
+ let srcpart := and(mload(_src), not(mask)) // Zero out src.
+ let destpart := and(mload(_dest), mask) // Retrieve the bytes.
+ mstore(_dest, or(destpart, srcpart))
+ }
+ }
+ }
+}
diff --git a/contracts/contracts/messageService/lib/TimeLock.sol b/contracts/contracts/messageService/lib/TimeLock.sol
new file mode 100644
index 000000000..4279edf15
--- /dev/null
+++ b/contracts/contracts/messageService/lib/TimeLock.sol
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: AGPL-3.0
+pragma solidity 0.8.19;
+
+import { TimelockController } from "@openzeppelin/contracts/governance/TimelockController.sol";
+
+/**
+ * @title TimeLock contract used to manage contract upgrades
+ * @author ConsenSys Software Inc.
+ * @notice This timelock contract will be the owner of all upgrades that gives users confidence and an ability to exit should they want to before an upgrade takes place
+ **/
+contract TimeLock is TimelockController {
+ constructor(
+ uint256 minDelay,
+ address[] memory proposers,
+ address[] memory executors,
+ address admin
+ ) TimelockController(minDelay, proposers, executors, admin) {}
+}
diff --git a/contracts/contracts/messageService/lib/TransactionDecoder.sol b/contracts/contracts/messageService/lib/TransactionDecoder.sol
new file mode 100644
index 000000000..22b8a8b6a
--- /dev/null
+++ b/contracts/contracts/messageService/lib/TransactionDecoder.sol
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: AGPL-3.0
+pragma solidity 0.8.19;
+
+import { RLPReader } from "./Rlp.sol";
+
+using RLPReader for RLPReader.RLPItem;
+using RLPReader for RLPReader.Iterator;
+using RLPReader for bytes;
+
+/*
+ * dev Thrown when the transaction data length is too short.
+ */
+error TransactionShort();
+
+/*
+ * dev Thrown when the transaction type is unknown.
+ */
+error UnknownTransactionType();
+
+/**
+ * @title Contract to decode RLP formatted transactions.
+ * @author ConsenSys Software Inc.
+ */
+library TransactionDecoder {
+ /**
+ * @notice Decodes the transaction extracting the calldata.
+ * @param _transaction The RLP transaction.
+ * @return data Returns the transaction calldata as bytes.
+ */
+ function decodeTransaction(bytes calldata _transaction) internal pure returns (bytes memory) {
+ if (_transaction.length < 1) {
+ revert TransactionShort();
+ }
+
+ bytes1 version = _transaction[0];
+
+ if (version == 0x01) {
+ return _decodeEIP2930Transaction(_transaction);
+ }
+
+ if (version == 0x02) {
+ return _decodeEIP1559Transaction(_transaction);
+ }
+
+ if (version >= 0xc0) {
+ return _decodeLegacyTransaction(_transaction);
+ }
+
+ revert UnknownTransactionType();
+ }
+
+ /**
+ * @notice Decodes the EIP1559 transaction extracting the calldata.
+ * @param _transaction The RLP transaction.
+ * @return data Returns the transaction calldata as bytes.
+ */
+ function _decodeEIP1559Transaction(bytes calldata _transaction) private pure returns (bytes memory data) {
+ bytes memory txData = _transaction[1:]; // skip the version byte
+
+ RLPReader.RLPItem memory rlp = txData._toRlpItem();
+ RLPReader.Iterator memory it = rlp._iterator();
+
+ data = it._skipTo(8)._toBytes();
+ }
+
+ /**
+ * @notice Decodes the EIP29230 transaction extracting the calldata.
+ * @param _transaction The RLP transaction.
+ * @return data Returns the transaction calldata as bytes.
+ */
+ function _decodeEIP2930Transaction(bytes calldata _transaction) private pure returns (bytes memory data) {
+ bytes memory txData = _transaction[1:]; // skip the version byte
+
+ RLPReader.RLPItem memory rlp = txData._toRlpItem();
+ RLPReader.Iterator memory it = rlp._iterator();
+
+ data = it._skipTo(7)._toBytes();
+ }
+
+ /**
+ * @notice Decodes the legacy transaction extracting the calldata.
+ * @param _transaction The RLP transaction.
+ * @return data Returns the transaction calldata as bytes.
+ */
+ function _decodeLegacyTransaction(bytes calldata _transaction) private pure returns (bytes memory data) {
+ bytes memory txData = _transaction;
+
+ RLPReader.RLPItem memory rlp = txData._toRlpItem();
+ RLPReader.Iterator memory it = rlp._iterator();
+
+ data = it._skipTo(6)._toBytes();
+ }
+}
diff --git a/contracts/contracts/test-contracts/IntegrationTestTrueVerifier.sol b/contracts/contracts/test-contracts/IntegrationTestTrueVerifier.sol
new file mode 100644
index 000000000..f85e2549f
--- /dev/null
+++ b/contracts/contracts/test-contracts/IntegrationTestTrueVerifier.sol
@@ -0,0 +1,11 @@
+// SPDX-License-Identifier: AGPL-3.0
+pragma solidity 0.8.19;
+
+import { IPlonkVerifier } from "../interfaces/IPlonkVerifier.sol";
+
+/// @dev Test verifier contract that returns true.
+contract IntegrationTestTrueVerifier is IPlonkVerifier {
+ function Verify(bytes calldata, uint256[] calldata) external pure returns (bool) {
+ return true;
+ }
+}
diff --git a/contracts/contracts/test-contracts/TestCodec.sol b/contracts/contracts/test-contracts/TestCodec.sol
new file mode 100644
index 000000000..f31a316b3
--- /dev/null
+++ b/contracts/contracts/test-contracts/TestCodec.sol
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: AGPL-3.0
+pragma solidity 0.8.19;
+
+import { CodecV2 } from "../messageService/lib/Codec.sol";
+import { IL2MessageManager } from "../interfaces/IL2MessageManager.sol";
+import { IMessageService } from "../interfaces/IMessageService.sol";
+
+contract TestCodecV2 {
+ function extractHashesTest(bytes calldata _data) external pure returns (bytes32[] memory) {
+ return CodecV2._extractXDomainAddHashes(_data);
+ }
+}
diff --git a/contracts/contracts/test-contracts/TestL1MessageManager.sol b/contracts/contracts/test-contracts/TestL1MessageManager.sol
new file mode 100644
index 000000000..240168ea3
--- /dev/null
+++ b/contracts/contracts/test-contracts/TestL1MessageManager.sol
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: AGPL-3.0
+pragma solidity 0.8.19;
+
+import { L1MessageManager } from "../messageService/l1/L1MessageManager.sol";
+
+contract TestL1MessageManager is L1MessageManager {
+ function addL2L1MessageHash(bytes32 _messageHash) external {
+ _addL2L1MessageHash(_messageHash);
+ }
+
+ function updateL2L1MessageStatusToClaimed(bytes32 _messageHash) external {
+ _updateL2L1MessageStatusToClaimed(_messageHash);
+ }
+
+ function addL1L2MessageHash(bytes32 _messageHash) external {
+ _addL1L2MessageHash(_messageHash);
+ }
+
+ function updateL1L2MessageStatusToReceived(bytes32[] calldata _messageHashes) external {
+ _updateL1L2MessageStatusToReceived(_messageHashes);
+ }
+}
diff --git a/contracts/contracts/test-contracts/TestL1MessageService.sol b/contracts/contracts/test-contracts/TestL1MessageService.sol
new file mode 100644
index 000000000..e64623bf2
--- /dev/null
+++ b/contracts/contracts/test-contracts/TestL1MessageService.sol
@@ -0,0 +1,94 @@
+// SPDX-License-Identifier: AGPL-3.0
+pragma solidity 0.8.19;
+
+import { L1MessageService } from "../messageService/l1/L1MessageService.sol";
+
+contract TestL1MessageService is L1MessageService {
+ address public originalSender;
+ bool private reentryDone;
+
+ function initialize(
+ address _limitManagerAddress,
+ address _pauserManagerAddress,
+ uint256 _rateLimitPeriod,
+ uint256 _rateLimitAmount
+ ) public initializer {
+ _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
+ __MessageService_init(_limitManagerAddress, _pauserManagerAddress, _rateLimitPeriod, _rateLimitAmount);
+ }
+
+ function tryInitialize(
+ address _limitManagerAddress,
+ address _pauserManagerAddress,
+ uint256 _rateLimitPeriod,
+ uint256 _rateLimitAmount
+ ) external {
+ __MessageService_init(_limitManagerAddress, _pauserManagerAddress, _rateLimitPeriod, _rateLimitAmount);
+ }
+
+ // @dev - the this. sendMessage is because the function is an "external" call and not wrapped
+ function canSendMessage(address _to, uint256 _fee, bytes calldata _calldata) external payable {
+ this.sendMessage{ value: msg.value }(_to, _fee, _calldata);
+ }
+
+ function addL2L1MessageHash(bytes32 _messageHash) external {
+ _addL2L1MessageHash(_messageHash);
+ }
+
+ function setSender() external payable {
+ (bool success, bytes memory data) = msg.sender.call(abi.encodeWithSignature("sender()"));
+ if (success) {
+ originalSender = abi.decode(data, (address));
+ }
+ }
+
+ function sendNewMessage() external payable {
+ this.sendMessage{ value: 1 wei }(address(this), 1, "0x");
+ }
+
+ function doReentry() external payable {
+ address originalAddress;
+
+ (bool success, bytes memory data) = msg.sender.call(abi.encodeWithSignature("sender()"));
+ if (success) {
+ originalAddress = abi.decode(data, (address));
+ }
+
+ if (!reentryDone) {
+ (bool succeeded, bytes memory dataInner) = msg.sender.call(
+ abi.encodeWithSignature(
+ "claimMessage(address,address,uint256,uint256,address,bytes,uint256)",
+ originalAddress,
+ originalAddress,
+ 0.05 ether,
+ 1 ether,
+ address(0),
+ abi.encodeWithSignature("doReentry()", 1)
+ )
+ );
+
+ if (succeeded) {
+ reentryDone = true;
+ } else {
+ if (dataInner.length > 0) {
+ assembly {
+ let data_size := mload(dataInner)
+ revert(add(32, dataInner), data_size)
+ }
+ } else {
+ revert("Function call reverted");
+ }
+ }
+ }
+ }
+
+ function addFunds() external payable {}
+
+ fallback() external payable {
+ revert();
+ }
+
+ receive() external payable override {
+ revert();
+ }
+}
diff --git a/contracts/contracts/test-contracts/TestL2MessageManager.sol b/contracts/contracts/test-contracts/TestL2MessageManager.sol
new file mode 100644
index 000000000..03d6027dd
--- /dev/null
+++ b/contracts/contracts/test-contracts/TestL2MessageManager.sol
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: AGPL-3.0
+pragma solidity 0.8.19;
+
+import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
+import { L2MessageManager } from "../messageService/l2/L2MessageManager.sol";
+import { IGenericErrors } from "../interfaces/IGenericErrors.sol";
+
+contract TestL2MessageManager is Initializable, L2MessageManager, IGenericErrors {
+ /// @custom:oz-upgrades-unsafe-allow constructor
+ constructor() {
+ _disableInitializers();
+ }
+
+ function initialize(address _pauserManager, address _l1l2MessageSetter) public initializer {
+ if (_pauserManager == address(0)) {
+ revert ZeroAddressNotAllowed();
+ }
+
+ if (_l1l2MessageSetter == address(0)) {
+ revert ZeroAddressNotAllowed();
+ }
+
+ __ERC165_init();
+ __Context_init();
+ __AccessControl_init();
+ __L2MessageManager_init(_l1l2MessageSetter);
+
+ _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
+ _grantRole(PAUSE_MANAGER_ROLE, _pauserManager);
+ }
+
+ function tryInitialize(address _l1l2MessageSetter) external {
+ __L2MessageManager_init(_l1l2MessageSetter);
+ }
+
+ function updateL1L2MessageStatusToClaimed(bytes32 _messageHash) external {
+ _updateL1L2MessageStatusToClaimed(_messageHash);
+ }
+}
diff --git a/contracts/contracts/test-contracts/TestL2MessageService.sol b/contracts/contracts/test-contracts/TestL2MessageService.sol
new file mode 100644
index 000000000..2a1b7a592
--- /dev/null
+++ b/contracts/contracts/test-contracts/TestL2MessageService.sol
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: AGPL-3.0
+pragma solidity 0.8.19;
+
+import { L2MessageService } from "../messageService/l2/L2MessageService.sol";
+
+contract TestL2MessageService is L2MessageService {
+ address public originalSender;
+ bool private reentryDone;
+
+ function canSendMessage(address _to, uint256 _fee, bytes calldata _calldata) external payable {
+ this.sendMessage{ value: msg.value }(_to, _fee, _calldata);
+ }
+
+ function setSender() external payable {
+ (bool success, bytes memory data) = msg.sender.call(abi.encodeWithSignature("sender()"));
+ if (success) {
+ originalSender = abi.decode(data, (address));
+ }
+ }
+
+ function callMessageServiceBase(address _messageServiceBase) external payable {
+ (bool success, ) = _messageServiceBase.call(abi.encodeWithSignature("withOnlyMessagingService()"));
+ if (!success) {
+ revert("Not authorized");
+ }
+ }
+
+ function doReentry() external payable {
+ address originalAddress;
+
+ (bool success, bytes memory data) = msg.sender.call(abi.encodeWithSignature("sender()"));
+ if (success) {
+ originalAddress = abi.decode(data, (address));
+ }
+
+ if (!reentryDone) {
+ (bool succeeded, bytes memory dataInner) = msg.sender.call(
+ abi.encodeWithSignature(
+ "claimMessage(address,address,uint256,uint256,address,bytes,uint256)",
+ originalAddress,
+ originalAddress,
+ 0.05 ether,
+ 1 ether,
+ address(0),
+ abi.encodeWithSignature("doReentry()")
+ )
+ );
+
+ if (succeeded) {
+ reentryDone = true;
+ } else {
+ if (dataInner.length > 0) {
+ assembly {
+ let data_size := mload(dataInner)
+ revert(add(32, dataInner), data_size)
+ }
+ } else {
+ revert("Function call reverted");
+ }
+ }
+ }
+ }
+
+ function addFunds() external payable {}
+
+ fallback() external payable {
+ revert();
+ }
+
+ receive() external payable override {
+ revert();
+ }
+}
diff --git a/contracts/contracts/test-contracts/TestMessageServiceBase.sol b/contracts/contracts/test-contracts/TestMessageServiceBase.sol
new file mode 100644
index 000000000..ac7a48346
--- /dev/null
+++ b/contracts/contracts/test-contracts/TestMessageServiceBase.sol
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: AGPL-3.0
+pragma solidity 0.8.19;
+
+import { MessageServiceBase } from "../messageService/MessageServiceBase.sol";
+
+contract TestMessageServiceBase is MessageServiceBase {
+ function initialize(address _messageService, address _remoteSender) external initializer {
+ __MessageServiceBase_init(_messageService);
+ _setRemoteSender(_remoteSender);
+ }
+
+ function withOnlyMessagingService() external onlyMessagingService {}
+
+ function withOnlyAuthorizedRemoteSender() external onlyAuthorizedRemoteSender {}
+
+ function tryInitialize(address _messageService, address _remoteSender) external {
+ __MessageServiceBase_init(_messageService);
+ _setRemoteSender(_remoteSender);
+ }
+}
diff --git a/contracts/contracts/test-contracts/TestPauseManager.sol b/contracts/contracts/test-contracts/TestPauseManager.sol
new file mode 100644
index 000000000..6eb2564a9
--- /dev/null
+++ b/contracts/contracts/test-contracts/TestPauseManager.sol
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: AGPL-3.0
+
+pragma solidity 0.8.19;
+
+import { PauseManager } from "../messageService/lib/PauseManager.sol";
+
+contract TestPauseManager is PauseManager {
+ function initialize() public initializer {
+ __AccessControl_init();
+ _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
+ }
+}
diff --git a/contracts/contracts/test-contracts/TestRateLimiter.sol b/contracts/contracts/test-contracts/TestRateLimiter.sol
new file mode 100644
index 000000000..42272a2b7
--- /dev/null
+++ b/contracts/contracts/test-contracts/TestRateLimiter.sol
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: AGPL-3.0
+pragma solidity 0.8.19;
+
+import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
+import { RateLimiter } from "../messageService/lib/RateLimiter.sol";
+
+contract TestRateLimiter is Initializable, RateLimiter {
+ // we need eth to test the limits with
+ function initialize(uint256 _periodInSeconds, uint256 _limitInWei) public initializer {
+ __AccessControl_init();
+
+ __RateLimiter_init(_periodInSeconds, _limitInWei);
+ _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
+ }
+
+ // @dev this is needed to get at the internal function
+ function withdrawSomeAmount(uint256 _amount) external {
+ _addUsedAmount(_amount);
+ }
+
+ function tryInitialize(uint256 _periodInSeconds, uint256 _limitInWei) external {
+ __RateLimiter_init(_periodInSeconds, _limitInWei);
+ }
+}
diff --git a/contracts/contracts/test-contracts/TestReceivingContract.sol b/contracts/contracts/test-contracts/TestReceivingContract.sol
new file mode 100644
index 000000000..7957a4165
--- /dev/null
+++ b/contracts/contracts/test-contracts/TestReceivingContract.sol
@@ -0,0 +1,9 @@
+// SPDX-License-Identifier: AGPL-3.0
+
+pragma solidity 0.8.19;
+
+contract TestReceivingContract {
+ fallback() external payable {}
+
+ receive() external payable {}
+}
diff --git a/contracts/contracts/test-contracts/TestRlp.sol b/contracts/contracts/test-contracts/TestRlp.sol
new file mode 100644
index 000000000..79055e7a9
--- /dev/null
+++ b/contracts/contracts/test-contracts/TestRlp.sol
@@ -0,0 +1,119 @@
+// SPDX-License-Identifier: AGPL-3.0
+pragma solidity 0.8.19;
+
+import { RLPReader } from "../messageService/lib/Rlp.sol";
+
+using RLPReader for RLPReader.RLPItem;
+using RLPReader for RLPReader.Iterator;
+using RLPReader for bytes;
+
+contract TestRlp {
+ uint8 internal constant STRING_SHORT_START = 0x80;
+ uint8 internal constant STRING_LONG_START = 0xb8;
+ uint8 internal constant LIST_SHORT_START = 0xc0;
+ uint8 internal constant LIST_LONG_START = 0xf8;
+ uint8 internal constant WORD_SIZE = 32;
+
+ function next(bytes memory _self) external pure returns (RLPReader.RLPItem memory nextItem, uint256 itemNextMemPtr) {
+ RLPReader.RLPItem memory rlpItem = _self._toRlpItem();
+ RLPReader.Iterator memory it = rlpItem._iterator();
+ uint256 itemNextPtr = it.nextPtr;
+ return (it._next(), itemNextPtr);
+ }
+
+ function hasNext(bytes memory _self) external pure returns (bool) {
+ RLPReader.RLPItem memory rlpItem = _self._toRlpItem();
+ RLPReader.Iterator memory it = rlpItem._iterator();
+ return it._hasNext();
+ }
+
+ function skipTo(bytes memory _self, uint256 _skipToNum) external pure returns (RLPReader.RLPItem memory item) {
+ RLPReader.RLPItem memory rlpItem = _self._toRlpItem();
+ RLPReader.Iterator memory it = rlpItem._iterator();
+ return it._skipTo(_skipToNum);
+ }
+
+ function skipToReturnBytes(bytes memory _self, uint256 _skipToNum) external pure returns (bytes memory item) {
+ RLPReader.RLPItem memory rlpItem = _self._toRlpItem();
+ RLPReader.Iterator memory it = rlpItem._iterator();
+ return it._skipTo(_skipToNum)._toBytes();
+ }
+
+ function fromRlpItemToAddress(RLPReader.RLPItem memory _item) external pure returns (address) {
+ return _item._toAddress();
+ }
+
+ function iterator(bytes memory _item) external pure {
+ _item._toRlpItem()._iterator();
+ }
+
+ function payloadLocation(
+ bytes memory _item
+ ) external pure returns (uint256 ptr, uint256 itemlen, uint256 rlpItemPtr) {
+ RLPReader.RLPItem memory rlpItem = _item._toRlpItem();
+ (uint memPtr, uint len) = rlpItem._payloadLocation();
+ return (memPtr, len, rlpItem.memPtr);
+ }
+
+ function isList(bytes memory _item) external pure returns (bool) {
+ RLPReader.RLPItem memory rlpItem = _item._toRlpItem();
+ return rlpItem._isList();
+ }
+
+ function toAddress(bytes memory _item) external pure returns (address) {
+ RLPReader.RLPItem memory rlpItem = _item._toRlpItem();
+ return rlpItem._toAddress();
+ }
+
+ function toUint(bytes memory _item) external pure returns (uint256 result) {
+ RLPReader.RLPItem memory rlpItem = _item._toRlpItem();
+ return rlpItem._toUint();
+ }
+
+ function toBytes(bytes memory _item) external pure returns (bytes memory result) {
+ RLPReader.RLPItem memory rlpItem = _item._toRlpItem();
+ return rlpItem._toBytes();
+ }
+
+ function itemLength(bytes memory item) external pure returns (uint) {
+ uint memPtr;
+ assembly {
+ memPtr := add(item, 0x20)
+ }
+
+ return _itemLength(memPtr);
+ }
+
+ function _itemLength(uint256 _memPtr) private pure returns (uint256) {
+ uint256 itemLen;
+ uint256 byte0;
+ assembly {
+ byte0 := byte(0, mload(_memPtr))
+ }
+
+ if (byte0 < STRING_SHORT_START) itemLen = 1;
+ else if (byte0 < STRING_LONG_START) itemLen = byte0 - STRING_SHORT_START + 1;
+ else if (byte0 < LIST_SHORT_START) {
+ assembly {
+ let byteLen := sub(byte0, 0xb7) // # Of bytes the actual length is.
+ _memPtr := add(_memPtr, 1) // Skip over the first byte.
+
+ /* 32 byte word size */
+ let dataLen := div(mload(_memPtr), exp(256, sub(32, byteLen))) // Right shifting to get the len.
+ itemLen := add(dataLen, add(byteLen, 1))
+ }
+ } else if (byte0 < LIST_LONG_START) {
+ itemLen = byte0 - LIST_SHORT_START + 1;
+ } else {
+ assembly {
+ let byteLen := sub(byte0, 0xf7)
+ _memPtr := add(_memPtr, 1)
+
+ let dataLen := div(mload(_memPtr), exp(256, sub(32, byteLen))) // Right shifting to the correct length.
+ itemLen := add(dataLen, add(byteLen, 1))
+ }
+ }
+
+ return itemLen;
+ }
+}
diff --git a/contracts/contracts/test-contracts/TestTransactionDecoder.sol b/contracts/contracts/test-contracts/TestTransactionDecoder.sol
new file mode 100644
index 000000000..9b363e2b8
--- /dev/null
+++ b/contracts/contracts/test-contracts/TestTransactionDecoder.sol
@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: AGPL-3.0
+pragma solidity 0.8.19;
+
+import { CodecV2 } from "../messageService/lib/Codec.sol";
+import { TransactionDecoder } from "../messageService/lib/TransactionDecoder.sol";
+
+contract TestTransactionDecoder {
+ using TransactionDecoder for *;
+
+ function decodeTransactionAndHashes(bytes calldata _data) external pure returns (bytes32[] memory) {
+ bytes memory transaction = TransactionDecoder.decodeTransaction(_data);
+ return CodecV2._extractXDomainAddHashes(transaction);
+ }
+}
diff --git a/contracts/contracts/test-contracts/TestZkevmV2.sol b/contracts/contracts/test-contracts/TestZkevmV2.sol
new file mode 100644
index 000000000..747f4a7b8
--- /dev/null
+++ b/contracts/contracts/test-contracts/TestZkevmV2.sol
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: AGPL-3.0
+pragma solidity 0.8.19;
+
+import { ZkEvmV2, TransactionDecoder, CodecV2 } from "../ZkEvmV2.sol";
+
+contract TestZkEvmV2 is ZkEvmV2 {
+ function addL1L2MessageHash(bytes calldata _rlpTx) external {
+ bytes memory data = TransactionDecoder.decodeTransaction(_rlpTx);
+
+ bytes32[] memory l1L2MessageHashes = CodecV2._extractXDomainAddHashes(data);
+
+ for (uint256 i; i < l1L2MessageHashes.length; i++) {
+ _addL1L2MessageHash(l1L2MessageHashes[i]);
+ }
+ }
+
+ function extractMessageHashes(bytes calldata _rlpTx) external pure returns (bytes32[] memory) {
+ bytes memory data = TransactionDecoder.decodeTransaction(_rlpTx);
+
+ bytes32[] memory l1L2MessageHashes = CodecV2._extractXDomainAddHashes(data);
+
+ return l1L2MessageHashes;
+ }
+
+ function processBlockTransactions(
+ bytes[] calldata _transactions,
+ uint16[] calldata _batchReceptionIndices
+ ) external returns (bytes32 hashOfTxHashes) {
+ return _processBlockTransactions(_transactions, _batchReceptionIndices);
+ }
+
+ function processMessageHashes(bytes32[] calldata _logs) external returns (bytes32 hashOfTxHashes) {
+ return _processMessageHashes(_logs);
+ }
+}
diff --git a/contracts/contracts/tokenBridge/BridgedToken.sol b/contracts/contracts/tokenBridge/BridgedToken.sol
new file mode 100644
index 000000000..63ab1365f
--- /dev/null
+++ b/contracts/contracts/tokenBridge/BridgedToken.sol
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: AGPL-3.0
+pragma solidity 0.8.19;
+
+import { ERC20PermitUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol";
+
+/**
+ * @title BridgedToken Contract
+ * @notice ERC20 token created when a native token is bridged to a target chain.
+ */
+contract BridgedToken is ERC20PermitUpgradeable {
+ address public bridge;
+ uint8 public _decimals;
+ /**
+ * @notice Initializes the BridgedToken contract.
+ * @dev Disables OpenZeppelin's initializer mechanism for safety.
+ */
+
+ /// @dev Keep free storage slots for future implementation updates to avoid storage collision.
+ uint256[50] private __gap;
+
+ error OnlyBridge(address bridgeAddress);
+
+ /// @dev Disable constructor for safety
+ /// @custom:oz-upgrades-unsafe-allow constructor
+ constructor() {
+ _disableInitializers();
+ }
+
+ function initialize(string memory _tokenName, string memory _tokenSymbol, uint8 _tokenDecimals) external initializer {
+ __ERC20_init(_tokenName, _tokenSymbol);
+ __ERC20Permit_init(_tokenName);
+ bridge = msg.sender;
+ _decimals = _tokenDecimals;
+ }
+
+ /// @dev Ensures call come from the bridge.
+ modifier onlyBridge() {
+ if (msg.sender != bridge) revert OnlyBridge(bridge);
+ _;
+ }
+
+ /**
+ * @dev Called by the bridge to mint tokens during a bridge transaction.
+ * @param _recipient The address to receive the minted tokens.
+ * @param _amount The amount of tokens to mint.
+ */
+ function mint(address _recipient, uint256 _amount) external onlyBridge {
+ _mint(_recipient, _amount);
+ }
+
+ /**
+ * @dev Called by the bridge to burn tokens during a bridge transaction.
+ * @dev User should first have allowed the bridge to spend tokens on their behalf.
+ * @param _account The account from which tokens will be burned.
+ * @param _amount The amount of tokens to burn.
+ */
+ function burn(address _account, uint256 _amount) external onlyBridge {
+ _spendAllowance(_account, msg.sender, _amount);
+ _burn(_account, _amount);
+ }
+
+ /**
+ * @dev Overrides ERC20 default function to support tokens with different decimals.
+ * @return The number of decimal.
+ */
+ function decimals() public view override returns (uint8) {
+ return _decimals;
+ }
+}
diff --git a/contracts/contracts/tokenBridge/TokenBridge.sol b/contracts/contracts/tokenBridge/TokenBridge.sol
new file mode 100644
index 000000000..d76999954
--- /dev/null
+++ b/contracts/contracts/tokenBridge/TokenBridge.sol
@@ -0,0 +1,498 @@
+// SPDX-License-Identifier: AGPL-3.0
+pragma solidity 0.8.19;
+
+import { ITokenBridge } from "./interfaces/ITokenBridge.sol";
+import { IMessageService } from "../interfaces/IMessageService.sol";
+
+import { IERC20PermitUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20PermitUpgradeable.sol";
+import { IERC20MetadataUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol";
+import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
+import { Ownable2StepUpgradeable } from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
+import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
+import { SafeERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
+import { BeaconProxy } from "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol";
+import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
+
+import { BridgedToken } from "./BridgedToken.sol";
+import { MessageServiceBase } from "../messageService/MessageServiceBase.sol";
+
+/**
+ * @title Linea Canonical Token Bridge
+ * @notice Contract to manage cross-chain ERC20 bridging.
+ * @author ConsenSys Software Inc.
+ */
+contract TokenBridge is
+ ITokenBridge,
+ PausableUpgradeable,
+ Ownable2StepUpgradeable,
+ MessageServiceBase,
+ ReentrancyGuardUpgradeable
+{
+ using SafeERC20Upgradeable for IERC20Upgradeable;
+
+ // solhint-disable-next-line var-name-mixedcase
+ bytes4 internal constant _PERMIT_SELECTOR =
+ bytes4(keccak256(bytes("permit(address,address,uint256,uint256,uint8,bytes32,bytes32)")));
+
+ /// @notice used for the token metadata
+ bytes private constant METADATA_NAME = abi.encodeCall(IERC20MetadataUpgradeable.name, ());
+ bytes private constant METADATA_SYMBOL = abi.encodeCall(IERC20MetadataUpgradeable.symbol, ());
+ bytes private constant METADATA_DECIMALS = abi.encodeCall(IERC20MetadataUpgradeable.decimals, ());
+
+ address public tokenBeacon;
+ /// @notice mapping (chainId => nativeTokenAddress => brigedTokenAddress)
+ mapping(uint256 => mapping(address => address)) public nativeToBridgedToken;
+ /// @notice mapping (brigedTokenAddress => nativeTokenAddress)
+ mapping(address => address) public bridgedToNativeToken;
+
+ /// @notice The current layer chainId from where the bridging is triggered
+ uint256 public sourceChainId;
+ /// @notice The targeted layer chainId where the bridging is received
+ uint256 public targetChainId;
+
+ // Special addresses used in the mappings to mark specific states for tokens.
+ /// @notice EMPTY means a token is not present in the mapping.
+ address internal constant EMPTY = address(0x0);
+ /// @notice RESERVED means a token is reserved and cannot be bridged.
+ address internal constant RESERVED_STATUS = address(0x111);
+ /// @notice NATIVE means a token is native to the current local chain.
+ address internal constant NATIVE_STATUS = address(0x222);
+ /// @notice DEPLOYED means the bridged token contract has been deployed on the remote chain.
+ address internal constant DEPLOYED_STATUS = address(0x333);
+
+ /// @dev Keep free storage slots for future implementation updates to avoid storage collision.
+ uint256[50] private __gap;
+
+ /// @dev Ensures the token has not been bridged before.
+ modifier isNewToken(address _token) {
+ if (nativeToBridgedToken[sourceChainId][_token] != EMPTY || bridgedToNativeToken[_token] != EMPTY)
+ revert AlreadyBridgedToken(_token);
+ _;
+ }
+
+ /**
+ * @dev Ensures the address is not address(0).
+ * @param _addr Address to check.
+ */
+ modifier nonZeroAddress(address _addr) {
+ if (_addr == EMPTY) revert ZeroAddressNotAllowed();
+ _;
+ }
+ /**
+ * @dev Ensures the amount is not 0.
+ * @param _amount amount to check.
+ */
+ modifier nonZeroAmount(uint256 _amount) {
+ if (_amount == 0) revert ZeroAmountNotAllowed(_amount);
+ _;
+ }
+
+ /// @dev Disable constructor for safety
+ /// @custom:oz-upgrades-unsafe-allow constructor
+ constructor() {
+ _disableInitializers();
+ }
+
+ /**
+ * @dev Contract will be used as proxy implementation.
+ * @param _messageService The address of the MessageService contract.
+ * @param _tokenBeacon The address of the tokenBeacon.
+ * @param _sourceChainId The source chain id of the current layer
+ * @param _targetChainId The target chaind id of the targeted layer
+ * @param _reservedTokens The list of reserved tokens to be set
+ */
+ function initialize(
+ address _securityCouncil,
+ address _messageService,
+ address _tokenBeacon,
+ uint256 _sourceChainId,
+ uint256 _targetChainId,
+ address[] calldata _reservedTokens
+ ) external nonZeroAddress(_securityCouncil) nonZeroAddress(_messageService) nonZeroAddress(_tokenBeacon) initializer {
+ __Pausable_init();
+ __Ownable2Step_init();
+ __MessageServiceBase_init(_messageService);
+ __ReentrancyGuard_init();
+ tokenBeacon = _tokenBeacon;
+ sourceChainId = _sourceChainId;
+ targetChainId = _targetChainId;
+
+ unchecked {
+ for (uint256 i; i < _reservedTokens.length; ) {
+ if (_reservedTokens[i] == EMPTY) revert ZeroAddressNotAllowed();
+ setReserved(_reservedTokens[i]);
+ ++i;
+ }
+ }
+ _transferOwnership(_securityCouncil);
+ }
+
+ /**
+ * @notice This function is the single entry point to bridge tokens to the
+ * other chain, both for native and already bridged tokens. You can use it
+ * to bridge any ERC20. If the token is bridged for the first time an ERC20
+ * (BridgedToken.sol) will be automatically deployed on the target chain.
+ * @dev User should first allow the bridge to transfer tokens on his behalf.
+ * Alternatively, you can use BridgeTokenWithPermit to do so in a single
+ * transaction. If you want the transfer to be automatically executed on the
+ * destination chain. You should send enough ETH to pay the postman fees.
+ * Note that Linea can reserve some tokens (which use a dedicated bridge).
+ * In this case, the token cannot be bridged. Linea can only reserve tokens
+ * that have not been bridged yet.
+ * Linea can pause the bridge for security reason. In this case new bridge
+ * transaction would revert.
+ * @param _token The address of the token to be bridged.
+ * @param _amount The amount of the token to be bridged.
+ * @param _recipient The address that will receive the tokens on the other chain.
+ */
+ function bridgeToken(
+ address _token,
+ uint256 _amount,
+ address _recipient
+ ) public payable nonZeroAddress(_token) nonZeroAddress(_recipient) nonZeroAmount(_amount) whenNotPaused nonReentrant {
+ address nativeMappingValue = nativeToBridgedToken[sourceChainId][_token];
+
+ if (nativeMappingValue == RESERVED_STATUS) {
+ // Token is reserved
+ revert ReservedToken(_token);
+ }
+
+ address bridgedMappingValue = bridgedToNativeToken[_token];
+ address nativeToken;
+ uint256 chainId;
+ bytes memory tokenMetadata;
+
+ if (bridgedMappingValue != EMPTY) {
+ // Token is bridged
+ BridgedToken(_token).burn(msg.sender, _amount);
+ nativeToken = bridgedMappingValue;
+ chainId = targetChainId;
+ } else {
+ // Token is native
+
+ // For tokens with special fee logic, ensure that only the amount received
+ // by the bridge will be minted on the target chain.
+ uint256 balanceBefore = IERC20Upgradeable(_token).balanceOf(address(this));
+ IERC20Upgradeable(_token).safeTransferFrom(msg.sender, address(this), _amount);
+ _amount = IERC20Upgradeable(_token).balanceOf(address(this)) - balanceBefore;
+
+ nativeToken = _token;
+
+ if (nativeMappingValue == EMPTY) {
+ // New token
+ nativeToBridgedToken[sourceChainId][_token] = NATIVE_STATUS;
+ emit NewToken(_token);
+ }
+
+ // Send Metadata only when the token has not been deployed on the other chain yet
+ if (nativeMappingValue != DEPLOYED_STATUS) {
+ tokenMetadata = abi.encode(_safeName(_token), _safeSymbol(_token), _safeDecimals(_token));
+ }
+ chainId = sourceChainId;
+ }
+
+ messageService.sendMessage{ value: msg.value }(
+ remoteSender,
+ msg.value, // fees
+ abi.encodeCall(ITokenBridge.completeBridging, (nativeToken, _amount, _recipient, chainId, tokenMetadata))
+ );
+ emit BridgingInitiated(msg.sender, _recipient, _token, _amount);
+ }
+
+ /**
+ * @notice Similar to `bridgeToken` function but allows to pass additional
+ * permit data to do the ERC20 approval in a single transaction.
+ * @notice _permit can fail silently, don't rely on this function passing as a form
+ * of authentication
+ * @param _token The address of the token to be bridged.
+ * @param _amount The amount of the token to be bridged.
+ * @param _recipient The address that will receive the tokens on the other chain.
+ * @param _permitData The permit data for the token, if applicable.
+ */
+ function bridgeTokenWithPermit(
+ address _token,
+ uint256 _amount,
+ address _recipient,
+ bytes calldata _permitData
+ ) external payable nonZeroAddress(_token) nonZeroAmount(_amount) whenNotPaused {
+ if (_permitData.length != 0) {
+ _permit(_token, _permitData);
+ }
+ bridgeToken(_token, _amount, _recipient);
+ }
+
+ /**
+ * @dev It can only be called from the Message Service. To finalize the bridging
+ * process, a user or postman needs to use the `claimMessage` function of the
+ * Message Service to trigger the transaction.
+ * @param _nativeToken The address of the token on its native chain.
+ * @param _amount The amount of the token to be received.
+ * @param _recipient The address that will receive the tokens.
+ * @param _chainId The token's origin layer chaindId
+ * @param _tokenMetadata Additional data used to deploy the bridged token if it
+ * doesn't exist already.
+ */
+ function completeBridging(
+ address _nativeToken,
+ uint256 _amount,
+ address _recipient,
+ uint256 _chainId,
+ bytes calldata _tokenMetadata
+ ) external nonReentrant onlyMessagingService onlyAuthorizedRemoteSender whenNotPaused {
+ address nativeMappingValue = nativeToBridgedToken[_chainId][_nativeToken];
+ address bridgedToken;
+
+ if (nativeMappingValue == NATIVE_STATUS || nativeMappingValue == DEPLOYED_STATUS) {
+ // Token is native on the local chain
+ IERC20Upgradeable(_nativeToken).safeTransfer(_recipient, _amount);
+ } else {
+ bridgedToken = nativeMappingValue;
+ if (nativeMappingValue == EMPTY) {
+ // New token
+ bridgedToken = deployBridgedToken(_nativeToken, _tokenMetadata, sourceChainId);
+ bridgedToNativeToken[bridgedToken] = _nativeToken;
+ nativeToBridgedToken[targetChainId][_nativeToken] = bridgedToken;
+ }
+ BridgedToken(bridgedToken).mint(_recipient, _amount);
+ }
+ emit BridgingFinalized(_nativeToken, bridgedToken, _amount, _recipient);
+ }
+
+ /**
+ * @dev Change the address of the Message Service.
+ * @param _messageService The address of the new Message Service.
+ */
+ function setMessageService(address _messageService) public nonZeroAddress(_messageService) onlyOwner {
+ address oldMessageService = address(messageService);
+ messageService = IMessageService(_messageService);
+ emit MessageServiceUpdated(_messageService, oldMessageService, msg.sender);
+ }
+
+ /**
+ * @dev Change the status to DEPLOYED to the tokens passed in parameter
+ * Will call the method setDeployed on the other chain using the message Service
+ * @param _tokens Array of bridged tokens that have been deployed.
+ */
+ function confirmDeployment(address[] memory _tokens) external payable {
+ // Check that the tokens have actually been deployed
+ for (uint256 i; i < _tokens.length; i++) {
+ address nativeToken = bridgedToNativeToken[_tokens[i]];
+ if (nativeToken == EMPTY) {
+ revert TokenNotDeployed(_tokens[i]);
+ }
+ _tokens[i] = nativeToken;
+ }
+
+ messageService.sendMessage{ value: msg.value }(
+ remoteSender,
+ msg.value, // fees
+ abi.encodeCall(ITokenBridge.setDeployed, (_tokens))
+ );
+
+ emit DeploymentConfirmed(_tokens, msg.sender);
+ }
+
+ /**
+ * @dev Change the status of tokens to DEPLOYED. New bridge transaction will not
+ * contain token metadata, which save gas.
+ * Can only be called from the Message Service. A user or postman needs to use
+ * the `claimMessage` function of the Message Service to trigger the transaction.
+ * @param _nativeTokens Array of native tokens for which the DEPLOYED status must be set.
+ */
+ function setDeployed(address[] memory _nativeTokens) external onlyMessagingService onlyAuthorizedRemoteSender {
+ address nativeToken;
+ unchecked {
+ for (uint256 i; i < _nativeTokens.length; ) {
+ nativeToken = _nativeTokens[i];
+ nativeToBridgedToken[sourceChainId][_nativeTokens[i]] = DEPLOYED_STATUS;
+ emit TokenDeployed(_nativeTokens[i]);
+ ++i;
+ }
+ }
+ }
+
+ /**
+ * @dev Sets the address of the remote token bridge. Can only be called once.
+ * @param _remoteTokenBridge The address of the remote token bridge to be set.
+ */
+ function setRemoteTokenBridge(address _remoteTokenBridge) external onlyOwner {
+ if (remoteSender != EMPTY) revert RemoteTokenBridgeAlreadySet(remoteSender);
+ _setRemoteSender(_remoteTokenBridge);
+ emit RemoteTokenBridgeSet(_remoteTokenBridge, msg.sender);
+ }
+
+ /**
+ * @dev Deploy a new EC20 contract for bridged token using a beacon proxy pattern.
+ * To adapt to future requirements, Linea can update the implementation of
+ * all (existing and future) contracts by updating the beacon. This update is
+ * subject to a delay by a time lock.
+ * Contracts are deployed using CREATE2 so deployment address is deterministic.
+ * @param _nativeToken The address of the native token on the source chain.
+ * @param _tokenMetadata The encoded metadata for the token.
+ * @param _chainId The chain id on which the token will be deployed, used to calculate the salt
+ * @return The address of the newly deployed BridgedToken contract.
+ */
+ function deployBridgedToken(
+ address _nativeToken,
+ bytes calldata _tokenMetadata,
+ uint256 _chainId
+ ) internal returns (address) {
+ bytes32 _salt = keccak256(abi.encode(_chainId, _nativeToken));
+ BeaconProxy bridgedToken = new BeaconProxy{ salt: _salt }(tokenBeacon, "");
+ address bridgedTokenAddress = address(bridgedToken);
+
+ (string memory name, string memory symbol, uint8 decimals) = abi.decode(_tokenMetadata, (string, string, uint8));
+ BridgedToken(bridgedTokenAddress).initialize(name, symbol, decimals);
+ emit NewTokenDeployed(bridgedTokenAddress, _nativeToken);
+ return bridgedTokenAddress;
+ }
+
+ /**
+ * @dev Linea can reserve tokens. In this case, the token cannot be bridged.
+ * Linea can only reserve tokens that have not been bridged before.
+ * @notice Make sure that _token is native to the current chain
+ * where you are calling this function from
+ * @param _token The address of the token to be set as reserved.
+ */
+ function setReserved(address _token) public nonZeroAddress(_token) onlyOwner isNewToken(_token) {
+ nativeToBridgedToken[sourceChainId][_token] = RESERVED_STATUS;
+ emit TokenReserved(_token);
+ }
+
+ /**
+ * @dev Removes a token from the reserved list.
+ * @param _token The address of the token to be removed from the reserved list.
+ */
+ function removeReserved(address _token) external nonZeroAddress(_token) onlyOwner {
+ if (nativeToBridgedToken[sourceChainId][_token] != RESERVED_STATUS) revert NotReserved(_token);
+ nativeToBridgedToken[sourceChainId][_token] = EMPTY;
+ }
+
+ /**
+ * @dev Linea can set a custom ERC20 contract for specific ERC20.
+ * For security purpose, Linea can only call this function if the token has
+ * not been bridged yet.
+ * @param _nativeToken The address of the token on the source chain.
+ * @param _targetContract The address of the custom contract.
+ */
+ function setCustomContract(
+ address _nativeToken,
+ address _targetContract
+ ) external nonZeroAddress(_nativeToken) nonZeroAddress(_targetContract) onlyOwner isNewToken(_nativeToken) {
+ if (bridgedToNativeToken[_targetContract] != EMPTY) {
+ revert AlreadyBrigedToNativeTokenSet(_targetContract);
+ }
+ if (_targetContract == NATIVE_STATUS || _targetContract == DEPLOYED_STATUS || _targetContract == RESERVED_STATUS) {
+ revert StatusAddressNotAllowed(_targetContract);
+ }
+ nativeToBridgedToken[targetChainId][_nativeToken] = _targetContract;
+ bridgedToNativeToken[_targetContract] = _nativeToken;
+ emit CustomContractSet(_nativeToken, _targetContract, msg.sender);
+ }
+
+ /**
+ * @dev Pause the contract, can only be called by the owner.
+ */
+ function pause() external onlyOwner {
+ _pause();
+ }
+
+ /**
+ * @dev Unpause the contract, can only be called by the owner.
+ */
+ function unpause() external onlyOwner {
+ _unpause();
+ }
+
+ // Helpers to safely get the metadata from a token, inspired by
+ // https://github.com/traderjoe-xyz/joe-core/blob/main/contracts/MasterChefJoeV3.sol#L55-L95
+
+ /**
+ * @dev Provides a safe ERC20.name version which returns 'NO_NAME' as fallback string.
+ * @param _token The address of the ERC-20 token contract
+ */
+ function _safeName(address _token) internal view returns (string memory) {
+ (bool success, bytes memory data) = _token.staticcall(METADATA_NAME);
+ return success ? _returnDataToString(data) : "NO_NAME";
+ }
+
+ /**
+ * @dev Provides a safe ERC20.symbol version which returns 'NO_SYMBOL' as fallback string
+ * @param _token The address of the ERC-20 token contract
+ */
+ function _safeSymbol(address _token) internal view returns (string memory) {
+ (bool success, bytes memory data) = _token.staticcall(METADATA_SYMBOL);
+ return success ? _returnDataToString(data) : "NO_SYMBOL";
+ }
+
+ /**
+ * @notice Provides a safe ERC20.decimals version which returns '18' as fallback value.
+ * Note Tokens with (decimals > 255) are not supported
+ * @param _token The address of the ERC-20 token contract
+ */
+ function _safeDecimals(address _token) internal view returns (uint8) {
+ (bool success, bytes memory data) = _token.staticcall(METADATA_DECIMALS);
+ return success && data.length == 32 ? abi.decode(data, (uint8)) : 18;
+ }
+
+ /**
+ * @dev Converts returned data to string. Returns 'NOT_VALID_ENCODING' as fallback value.
+ * @param _data returned data
+ */
+ function _returnDataToString(bytes memory _data) internal pure returns (string memory) {
+ if (_data.length >= 64) {
+ return abi.decode(_data, (string));
+ } else if (_data.length != 32) {
+ return "NOT_VALID_ENCODING";
+ }
+
+ // Since the strings on bytes32 are encoded left-right, check the first zero in the data
+ uint256 nonZeroBytes;
+ unchecked {
+ while (nonZeroBytes < 32 && _data[nonZeroBytes] != 0) {
+ nonZeroBytes++;
+ }
+ }
+
+ // If the first one is 0, we do not handle the encoding
+ if (nonZeroBytes == 0) {
+ return "NOT_VALID_ENCODING";
+ }
+ // Create a byte array with nonZeroBytes length
+ bytes memory bytesArray = new bytes(nonZeroBytes);
+ unchecked {
+ for (uint256 i; i < nonZeroBytes; ) {
+ bytesArray[i] = _data[i];
+ ++i;
+ }
+ }
+ return string(bytesArray);
+ }
+
+ /**
+ * @notice Call the token permit method of extended ERC20
+ * @notice Only support tokens implementing ERC-2612
+ * @param _token ERC20 token address
+ * @param _permitData Raw data of the call `permit` of the token
+ */
+ function _permit(address _token, bytes calldata _permitData) internal {
+ if (bytes4(_permitData[:4]) != _PERMIT_SELECTOR)
+ revert InvalidPermitData(bytes4(_permitData[:4]), _PERMIT_SELECTOR);
+ // Decode the permit data
+ // The parameters are:
+ // 1. owner: The address of the wallet holding the tokens
+ // 2. spender: The address of the entity permitted to spend the tokens
+ // 3. value: The maximum amount of tokens the spender is allowed to spend
+ // 4. deadline: The time until which the permit is valid
+ // 5. v: Part of the signature (along with r and s), these three values form the signature of the permit
+ // 6. r: Part of the signature
+ // 7. s: Part of the signature
+ (address owner, address spender, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s) = abi.decode(
+ _permitData[4:],
+ (address, address, uint256, uint256, uint8, bytes32, bytes32)
+ );
+ if (owner != msg.sender) revert PermitNotFromSender(owner);
+ if (spender != address(this)) revert PermitNotAllowingBridge(spender);
+ IERC20PermitUpgradeable(_token).permit(msg.sender, address(this), amount, deadline, v, r, s);
+ }
+}
diff --git a/contracts/contracts/tokenBridge/interfaces/ITokenBridge.sol b/contracts/contracts/tokenBridge/interfaces/ITokenBridge.sol
new file mode 100644
index 000000000..c99356c51
--- /dev/null
+++ b/contracts/contracts/tokenBridge/interfaces/ITokenBridge.sol
@@ -0,0 +1,116 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity 0.8.19;
+
+interface ITokenBridge {
+ event TokenReserved(address indexed token);
+ event CustomContractSet(address indexed nativeToken, address indexed customContract, address indexed setBy);
+ event BridgingInitiated(address indexed sender, address recipient, address indexed token, uint256 indexed amount);
+ event BridgingFinalized(
+ address indexed nativeToken,
+ address indexed bridgedToken,
+ uint256 indexed amount,
+ address recipient
+ );
+ event NewToken(address indexed token);
+ event NewTokenDeployed(address indexed bridgedToken, address indexed nativeToken);
+ event RemoteTokenBridgeSet(address indexed remoteTokenBridge, address indexed setBy);
+ event TokenDeployed(address indexed token);
+ event DeploymentConfirmed(address[] tokens, address indexed confirmedBy);
+ event MessageServiceUpdated(
+ address indexed newMessageService,
+ address indexed oldMessageService,
+ address indexed setBy
+ );
+
+ error ReservedToken(address token);
+ error RemoteTokenBridgeAlreadySet(address remoteTokenBridge);
+ error AlreadyBridgedToken(address token);
+ error InvalidPermitData(bytes4 permitData, bytes4 permitSelector);
+ error PermitNotFromSender(address owner);
+ error PermitNotAllowingBridge(address spender);
+ error ZeroAmountNotAllowed(uint256 amount);
+ error NotReserved(address token);
+ error TokenNotDeployed(address token);
+ error AlreadyBrigedToNativeTokenSet(address token);
+ error StatusAddressNotAllowed(address token);
+
+ /**
+ * @notice Similar to `bridgeToken` function but allows to pass additional
+ * permit data to do the ERC20 approval in a single transaction.
+ * @param _token The address of the token to be bridged.
+ * @param _amount The amount of the token to be bridged.
+ * @param _recipient The address that will receive the tokens on the other chain.
+ * @param _permitData The permit data for the token, if applicable.
+ */
+ function bridgeTokenWithPermit(
+ address _token,
+ uint256 _amount,
+ address _recipient,
+ bytes calldata _permitData
+ ) external payable;
+
+ /**
+ * @dev It can only be called from the Message Service. To finalize the bridging
+ * process, a user or postmen needs to use the `claimMessage` function of the
+ * Message Service to trigger the transaction.
+ * @param _nativeToken The address of the token on its native chain.
+ * @param _amount The amount of the token to be received.
+ * @param _recipient The address that will receive the tokens.
+ * @param _chainId The source chainId or target chaindId for this token
+ * @param _tokenMetadata Additional data used to deploy the bridged token if it
+ * doesn't exist already.
+ */
+ function completeBridging(
+ address _nativeToken,
+ uint256 _amount,
+ address _recipient,
+ uint256 _chainId,
+ bytes calldata _tokenMetadata
+ ) external;
+
+ /**
+ * @dev Change the address of the Message Service.
+ * @param _messageService The address of the new Message Service.
+ */
+ function setMessageService(address _messageService) external;
+
+ /**
+ * @dev It can only be called from the Message Service. To change the status of
+ * the native tokens to DEPLOYED meaning they have been deployed on the other chain
+ * a user or postman needs to use the `claimMessage` function of the
+ * Message Service to trigger the transaction.
+ * @param _nativeTokens The addresses of the native tokens.
+ */
+ function setDeployed(address[] memory _nativeTokens) external;
+
+ /**
+ * @dev Sets the address of the remote token bridge. Can only be called once.
+ * @param _remoteTokenBridge The address of the remote token bridge to be set.
+ */
+ function setRemoteTokenBridge(address _remoteTokenBridge) external;
+
+ /**
+ * @dev Removes a token from the reserved list.
+ * @param _token The address of the token to be removed from the reserved list.
+ */
+ function removeReserved(address _token) external;
+
+ /**
+ * @dev Linea can set a custom ERC20 contract for specific ERC20.
+ * For security purpose, Linea can only call this function if the token has
+ * not been bridged yet.
+ * @param _nativeToken address of the token on the source chain.
+ * @param _targetContract address of the custom contract.
+ */
+ function setCustomContract(address _nativeToken, address _targetContract) external;
+
+ /**
+ * @dev Pause the contract, can only be called by the owner.
+ */
+ function pause() external;
+
+ /**
+ * @dev Unpause the contract, can only be called by the owner.
+ */
+ function unpause() external;
+}
diff --git a/contracts/contracts/tokenBridge/mocks/ERC20MintBurn.sol b/contracts/contracts/tokenBridge/mocks/ERC20MintBurn.sol
new file mode 100644
index 000000000..ac3c198b5
--- /dev/null
+++ b/contracts/contracts/tokenBridge/mocks/ERC20MintBurn.sol
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity ^0.8.0;
+
+import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
+
+contract MockERC20MintBurn is ERC20 {
+ constructor(string memory _tokenName, string memory _tokenSymbol) ERC20(_tokenName, _tokenSymbol) {}
+
+ function mint(address _account, uint256 _amount) public returns (bool) {
+ _mint(_account, _amount);
+ return true;
+ }
+
+ function burn(address _account, uint256 _amount) public returns (bool) {
+ _burn(_account, _amount);
+ return true;
+ }
+}
diff --git a/contracts/contracts/tokenBridge/mocks/ERC20NoNameMintBurn.sol b/contracts/contracts/tokenBridge/mocks/ERC20NoNameMintBurn.sol
new file mode 100644
index 000000000..36f3c09f6
--- /dev/null
+++ b/contracts/contracts/tokenBridge/mocks/ERC20NoNameMintBurn.sol
@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity ^0.8.0;
+
+contract MockERC20NoNameMintBurn {
+ uint8 public decimals;
+
+ mapping(address => uint256) public balanceOf;
+ mapping(address => mapping(address => uint256)) public allowance;
+
+ event Transfer(address indexed from, address indexed to, uint256 value);
+ event Approval(address indexed owner, address indexed spender, uint256 value);
+ event Mint(address indexed account, uint256 value);
+
+ constructor() {
+ decimals = 18;
+ }
+
+ function transfer(address _to, uint256 _value) public returns (bool) {
+ require(balanceOf[msg.sender] >= _value, "Insufficient balance");
+ balanceOf[msg.sender] -= _value;
+ balanceOf[_to] += _value;
+ emit Transfer(msg.sender, _to, _value);
+ return true;
+ }
+
+ function approve(address _spender, uint256 _value) public returns (bool) {
+ allowance[msg.sender][_spender] = _value;
+ emit Approval(msg.sender, _spender, _value);
+ return true;
+ }
+
+ function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
+ require(balanceOf[_from] >= _value, "Insufficient balance");
+ require(allowance[_from][msg.sender] >= _value, "Insufficient allowance");
+ balanceOf[_from] -= _value;
+ balanceOf[_to] += _value;
+ allowance[_from][msg.sender] -= _value;
+ emit Transfer(_from, _to, _value);
+ return true;
+ }
+
+ function mint(address _account, uint256 _value) public returns (bool) {
+ balanceOf[_account] += _value;
+ emit Mint(_account, _value);
+ return true;
+ }
+}
diff --git a/contracts/contracts/tokenBridge/mocks/ERC20WeirdNameSymbol.sol b/contracts/contracts/tokenBridge/mocks/ERC20WeirdNameSymbol.sol
new file mode 100644
index 000000000..52df2a022
--- /dev/null
+++ b/contracts/contracts/tokenBridge/mocks/ERC20WeirdNameSymbol.sol
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity ^0.8.0;
+
+contract MockERC20WeirdNameSymbol {
+ uint8 public decimals;
+
+ mapping(address => uint256) public balanceOf;
+ mapping(address => mapping(address => uint256)) public allowance;
+
+ event Transfer(address indexed from, address indexed to, uint256 value);
+ event Approval(address indexed owner, address indexed spender, uint256 value);
+ event Mint(address indexed account, uint256 value);
+
+ constructor() {
+ decimals = 18;
+ }
+
+ function name() public pure returns (bytes1) {
+ return 0x00;
+ }
+
+ function symbol() public pure returns (bytes1) {
+ return 0x01;
+ }
+
+ function transfer(address _to, uint256 _value) public returns (bool) {
+ require(balanceOf[msg.sender] >= _value, "Insufficient balance");
+ balanceOf[msg.sender] -= _value;
+ balanceOf[_to] += _value;
+ emit Transfer(msg.sender, _to, _value);
+ return true;
+ }
+
+ function approve(address _spender, uint256 _value) public returns (bool) {
+ allowance[msg.sender][_spender] = _value;
+ emit Approval(msg.sender, _spender, _value);
+ return true;
+ }
+
+ function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
+ require(balanceOf[_from] >= _value, "Insufficient balance");
+ require(allowance[_from][msg.sender] >= _value, "Insufficient allowance");
+ balanceOf[_from] -= _value;
+ balanceOf[_to] += _value;
+ allowance[_from][msg.sender] -= _value;
+ emit Transfer(_from, _to, _value);
+ return true;
+ }
+
+ function mint(address _account, uint256 _value) public returns (bool) {
+ balanceOf[_account] += _value;
+ emit Mint(_account, _value);
+ return true;
+ }
+}
diff --git a/contracts/contracts/tokenBridge/mocks/ERCFees.sol b/contracts/contracts/tokenBridge/mocks/ERCFees.sol
new file mode 100644
index 000000000..58671596c
--- /dev/null
+++ b/contracts/contracts/tokenBridge/mocks/ERCFees.sol
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity ^0.8.0;
+
+import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
+
+uint16 constant FEES_PERCENTAGE_MULTIPLIER = 10000;
+
+contract ERC20Fees is ERC20 {
+ uint16 public feePercentage;
+
+ /**
+ * @dev Constructor that gives _msgSender() all of existing tokens.
+ * @param _tokenName string memory token name
+ * @param _tokenSymbol string memory token symbol
+ * @param _feePercentage uint16 fee percentage with FEE_PERCENTAGE_MULTIPLIER
+ */
+ constructor(
+ string memory _tokenName,
+ string memory _tokenSymbol,
+ uint16 _feePercentage
+ ) ERC20(_tokenName, _tokenSymbol) {
+ feePercentage = _feePercentage;
+ }
+
+ function mint(address _account, uint256 _amount) public returns (bool) {
+ _mint(_account, _amount);
+ return true;
+ }
+
+ function _transfer(address _sender, address _recipient, uint256 _amount) internal virtual override {
+ _burn(_sender, (_amount * feePercentage) / FEES_PERCENTAGE_MULTIPLIER);
+ super._transfer(
+ _sender,
+ _recipient,
+ (_amount * (FEES_PERCENTAGE_MULTIPLIER - feePercentage)) / FEES_PERCENTAGE_MULTIPLIER
+ );
+ }
+
+ function burn(address _account, uint256 _amount) public returns (bool) {
+ _burn(_account, _amount);
+ return true;
+ }
+}
diff --git a/contracts/contracts/tokenBridge/mocks/MessageBridgeV2/MockMessageServiceV2.sol b/contracts/contracts/tokenBridge/mocks/MessageBridgeV2/MockMessageServiceV2.sol
new file mode 100644
index 000000000..96116a77d
--- /dev/null
+++ b/contracts/contracts/tokenBridge/mocks/MessageBridgeV2/MockMessageServiceV2.sol
@@ -0,0 +1,63 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity 0.8.19;
+
+import { IMessageService } from "../../../interfaces/IMessageService.sol";
+import { IGenericErrors } from "../../../interfaces/IGenericErrors.sol";
+import { PauseManager } from "../../../messageService/lib/PauseManager.sol";
+import { L1MessageManager } from "../../../messageService/l1/L1MessageManager.sol";
+
+contract MockMessageServiceV2 is L1MessageManager, IMessageService, PauseManager, IGenericErrors {
+ address internal messageSender = address(0);
+ uint256 public nextMessageNumber = 1;
+
+ /**
+ * @notice Adds a message for sending cross-chain and emits MessageSent.
+ * @dev The message number is preset (nextMessageNumber) and only incremented at the end if successful for the next caller.
+ * @dev This function should be called with a msg.value = _value + _fee. The fee will be paid on the destination chain.
+ * @param _to The address the message is intended for.
+ * @param _fee The fee being paid for the message delivery.
+ * @param _calldata The calldata to pass to the recipient.
+ **/
+ function sendMessage(
+ address _to,
+ uint256 _fee,
+ bytes calldata _calldata
+ ) external payable whenTypeNotPaused(L1_L2_PAUSE_TYPE) whenTypeNotPaused(GENERAL_PAUSE_TYPE) {
+ if (_to == address(0)) {
+ revert ZeroAddressNotAllowed();
+ }
+
+ if (_fee > msg.value) {
+ revert ValueSentTooLow();
+ }
+
+ uint256 messageNumber = nextMessageNumber;
+ uint256 valueSent = msg.value - _fee;
+
+ bytes32 messageHash = keccak256(abi.encode(msg.sender, _to, _fee, valueSent, messageNumber, _calldata));
+
+ // @dev Status check and revert is in the message manager
+ _addL1L2MessageHash(messageHash);
+
+ nextMessageNumber++;
+
+ emit MessageSent(msg.sender, _to, _fee, valueSent, messageNumber, _calldata, messageHash);
+ }
+
+ // When called within the context of the delivered call returns the sender from the other layer
+ // otherwise returns the zero address
+ function sender() external view returns (address) {
+ return messageSender;
+ }
+
+ // Placeholder
+ function claimMessage(
+ address _from,
+ address _to,
+ uint256 _fee,
+ uint256 _value,
+ address payable _feeRecipient,
+ bytes calldata _calldata,
+ uint256 _nonce
+ ) external {}
+}
diff --git a/contracts/contracts/tokenBridge/mocks/MockMessageService.sol b/contracts/contracts/tokenBridge/mocks/MockMessageService.sol
new file mode 100644
index 000000000..1b0eeb926
--- /dev/null
+++ b/contracts/contracts/tokenBridge/mocks/MockMessageService.sol
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: AGPL-3.0
+pragma solidity 0.8.19;
+
+import { IMessageService } from "../../interfaces/IMessageService.sol";
+
+contract MockMessageService is IMessageService {
+ uint256 public constant CALL_GAS_LIMIT = 1000000;
+ address internal messageSender = address(0);
+
+ function sendMessage(address _to, uint256 _fee, bytes calldata _calldata) external payable {
+ require(msg.value >= _fee, "MessageService: Value too low");
+ messageSender = msg.sender;
+ uint256 _value = msg.value - _fee;
+ (bool success, bytes memory result) = _to.call{ value: _value, gas: CALL_GAS_LIMIT }(_calldata);
+
+ // This is used to return the same revert message as the contract called returns it
+ if (success == false) {
+ assembly {
+ revert(add(result, 32), mload(result))
+ }
+ }
+ }
+
+ // When called within the context of the delivered call returns the sender from the other layer
+ // otherwise returns the zero address
+ function sender() external view returns (address) {
+ return messageSender;
+ }
+
+ // Placeholder
+ function claimMessage(
+ address _from,
+ address _to,
+ uint256 _fee,
+ uint256 _value,
+ address payable _feeRecipient,
+ bytes calldata _calldata,
+ uint256 _nonce
+ ) external {}
+}
diff --git a/contracts/contracts/tokenBridge/mocks/MockTokenBridge.sol b/contracts/contracts/tokenBridge/mocks/MockTokenBridge.sol
new file mode 100644
index 000000000..a2ff67338
--- /dev/null
+++ b/contracts/contracts/tokenBridge/mocks/MockTokenBridge.sol
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: AGPL-3.0
+pragma solidity 0.8.19;
+
+import { TokenBridge } from "../TokenBridge.sol";
+
+contract MockTokenBridge is TokenBridge {
+ function setNativeMappingValue(address token, address value) external {
+ nativeToBridgedToken[1][token] = value;
+ }
+}
diff --git a/contracts/contracts/tokenBridge/mocks/Reentrancy/MaliciousERC777.sol b/contracts/contracts/tokenBridge/mocks/Reentrancy/MaliciousERC777.sol
new file mode 100644
index 000000000..7776ca90a
--- /dev/null
+++ b/contracts/contracts/tokenBridge/mocks/Reentrancy/MaliciousERC777.sol
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity ^0.8.0;
+
+import { TokenBridge } from "../../TokenBridge.sol";
+import { ReentrancyContract } from "./ReentrancyContract.sol";
+
+contract MaliciousERC777 {
+ mapping(address => uint256) public balanceOf;
+ ReentrancyContract private reentrancyContract;
+
+ constructor(address _reentrancyContract) {
+ reentrancyContract = ReentrancyContract(_reentrancyContract);
+ }
+
+ function mint(address _to, uint256 _amount) external {
+ balanceOf[_to] += _amount;
+ }
+
+ function transferFrom(address _from, address _to, uint256 _amount) external {
+ reentrancyContract.beforeTokenTransfer();
+
+ balanceOf[_from] -= _amount;
+ balanceOf[_to] += _amount;
+ }
+
+ function name() external pure returns (string memory) {
+ return "Token";
+ }
+
+ function symbol() external pure returns (string memory) {
+ return "Token";
+ }
+
+ function decimals() external pure returns (uint8) {
+ return 18;
+ }
+}
diff --git a/contracts/contracts/tokenBridge/mocks/Reentrancy/ReentrancyContract.sol b/contracts/contracts/tokenBridge/mocks/Reentrancy/ReentrancyContract.sol
new file mode 100644
index 000000000..9a071b18b
--- /dev/null
+++ b/contracts/contracts/tokenBridge/mocks/Reentrancy/ReentrancyContract.sol
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.0;
+
+import { TokenBridge } from "../../TokenBridge.sol";
+
+contract ReentrancyContract {
+ // The Linea `TokenBridge` contract
+ TokenBridge private tokenBridge;
+
+ // A simple ERC777 token with transfer hooks for this PoC
+ address private token;
+
+ // Counts how often we re-entered the bridge from `beforeTokenTransfer` below.
+ uint256 private counter;
+
+ constructor(address _tokenBridge) {
+ counter = 0;
+ tokenBridge = TokenBridge(_tokenBridge);
+ }
+
+ function setToken(address _token) external {
+ token = _token;
+ }
+
+ function beforeTokenTransfer() external {
+ counter++;
+ if (counter == 5) {
+ // Stop the re-entrancy loop
+ return;
+ } else if (counter == 4) {
+ // The final re-entrancy. Send the full token amount.
+ tokenBridge.bridgeToken(token, 20, address(this));
+ } else {
+ // Keep the loop going with 1 wei.
+ tokenBridge.bridgeToken(token, 1, address(this));
+ }
+ }
+}
diff --git a/contracts/contracts/tokenBridge/mocks/UpgradedBridgedToken.sol b/contracts/contracts/tokenBridge/mocks/UpgradedBridgedToken.sol
new file mode 100644
index 000000000..fcdca2bcf
--- /dev/null
+++ b/contracts/contracts/tokenBridge/mocks/UpgradedBridgedToken.sol
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: AGPL-3.0
+pragma solidity 0.8.19;
+
+import { BridgedToken } from "../BridgedToken.sol";
+
+contract UpgradedBridgedToken is BridgedToken {
+ function isUpgraded() external pure returns (bool) {
+ return true;
+ }
+}
diff --git a/contracts/contracts/verifiers/PlonkVerifier.sol b/contracts/contracts/verifiers/PlonkVerifier.sol
new file mode 100644
index 000000000..ae071f9ce
--- /dev/null
+++ b/contracts/contracts/verifiers/PlonkVerifier.sol
@@ -0,0 +1,948 @@
+// SPDX-License-Identifier: Apache-2.0
+
+// Copyright 2023 Consensys Software Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Code generated by gnark DO NOT EDIT
+
+pragma solidity ^0.8.0;
+
+pragma experimental ABIEncoderV2;
+
+import { Utils } from "./Utils.sol";
+
+contract PlonkVerifier {
+ using Utils for *;
+ uint256 private constant r_mod = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
+ uint256 private constant p_mod = 21888242871839275222246405745257275088696311157297823662689037894645226208583;
+
+ uint256 private constant g2_srs_0_x_0 = 11559732032986387107991004021392285783925812861821192530917403151452391805634;
+ uint256 private constant g2_srs_0_x_1 = 10857046999023057135944570762232829481370756359578518086990519993285655852781;
+ uint256 private constant g2_srs_0_y_0 = 4082367875863433681332203403145435568316851327593401208105741076214120093531;
+ uint256 private constant g2_srs_0_y_1 = 8495653923123431417604973247489272438418190587263600148770280649306958101930;
+
+ uint256 private constant g2_srs_1_x_0 = 8346649071297262948544714173736482699128410021416543801035997871711276407441;
+ uint256 private constant g2_srs_1_x_1 = 7883069657575422103991939149663123175414599384626279795595310520790051448551;
+ uint256 private constant g2_srs_1_y_0 = 16795962876692295166012804782785252840345796645199573986777498170046508450267;
+ uint256 private constant g2_srs_1_y_1 = 3343323372806643151863786479815504460125163176086666838570580800830972412274;
+
+ // ----------------------- vk ---------------------
+ uint256 private constant vk_domain_size = 8;
+ uint256 private constant vk_inv_domain_size =
+ 19152212512859365819465605027100115702479818850364030050735928663253832433665;
+ uint256 private constant vk_omega = 19540430494807482326159819597004422086093766032135589407132600596362845576832;
+ uint256 private constant vk_ql_com_x = 12713033106903929954962957782751037365493481650852301350699527462487678312730;
+ uint256 private constant vk_ql_com_y = 17316357290294957206371243791172587972859275298243944143809713861516234907550;
+ uint256 private constant vk_qr_com_x = 5662767044253740967755748980268174004856695908685938303013261144583083198446;
+ uint256 private constant vk_qr_com_y = 6382799101210166819729376204737829116947826435678474922598749354604097967209;
+ uint256 private constant vk_qm_com_x = 5193671095906975595734671634478269228821045538723117868079579313819098776630;
+ uint256 private constant vk_qm_com_y = 9673151517799982272629653302746201712406178140410428558679937505861604092187;
+ uint256 private constant vk_qo_com_x = 5193671095906975595734671634478269228821045538723117868079579313819098776630;
+ uint256 private constant vk_qo_com_y = 12215091354039292949616752442511073376290133016887395104009100388783622116396;
+ uint256 private constant vk_qk_com_x = 0;
+ uint256 private constant vk_qk_com_y = 0;
+
+ uint256 private constant vk_s1_com_x = 13265548266809483064817476840215577874286086124942417595919907621068573794860;
+ uint256 private constant vk_s1_com_y = 10206781836619688111537994210869537396148078377912097728368463332929901519072;
+
+ uint256 private constant vk_s2_com_x = 1197285824403341044832563393540923360785378635535885180873740164700469480193;
+ uint256 private constant vk_s2_com_y = 517247040122061559616601136836958567351546715715808621843822252892237739582;
+
+ uint256 private constant vk_s3_com_x = 7036937417871161786653298563922368440407466270878904767769299014710936082463;
+ uint256 private constant vk_s3_com_y = 435104539981111586131466741315282310627656861586494374779845247063954235462;
+
+ uint256 private constant vk_coset_shift = 5;
+
+ // TODO wait for the multi commit eval to auto generate the loop
+ uint256 private constant vk_selector_commitments_commit_api_0_x =
+ 16567240487186430955929027352069250215770500953378820817613604658309523150095;
+ uint256 private constant vk_selector_commitments_commit_api_0_y =
+ 3612413623648154893470548549593244260388222884336605465473791663873537757444;
+
+ function load_vk_commitments_indices_commit_api(uint256[] memory v) internal pure {
+ assembly ("memory-safe") {
+ let _v := add(v, 0x20)
+
+ mstore(_v, 5)
+ _v := add(_v, 0x20)
+ }
+ }
+
+ uint256 private constant vk_nb_commitments_commit_api = 1;
+
+ // ------------------------------------------------
+
+ // offset proof
+ uint256 private constant proof_l_com_x = 0x20;
+ uint256 private constant proof_l_com_y = 0x40;
+ uint256 private constant proof_r_com_x = 0x60;
+ uint256 private constant proof_r_com_y = 0x80;
+ uint256 private constant proof_o_com_x = 0xa0;
+ uint256 private constant proof_o_com_y = 0xc0;
+
+ // h = h_0 + x^{n+2}h_1 + x^{2(n+2)}h_2
+ uint256 private constant proof_h_0_x = 0xe0;
+ uint256 private constant proof_h_0_y = 0x100;
+ uint256 private constant proof_h_1_x = 0x120;
+ uint256 private constant proof_h_1_y = 0x140;
+ uint256 private constant proof_h_2_x = 0x160;
+ uint256 private constant proof_h_2_y = 0x180;
+
+ // wire values at zeta
+ uint256 private constant proof_l_at_zeta = 0x1a0;
+ uint256 private constant proof_r_at_zeta = 0x1c0;
+ uint256 private constant proof_o_at_zeta = 0x1e0;
+
+ //uint256[STATE_WIDTH-1] permutation_polynomials_at_zeta; // SĪ1(zeta),SĪ2(zeta)
+ uint256 private constant proof_s1_at_zeta = 0x200; // SĪ1(zeta)
+ uint256 private constant proof_s2_at_zeta = 0x220; // SĪ2(zeta)
+
+ //Bn254.G1Point grand_product_commitment; // [z(x)]
+ uint256 private constant proof_grand_product_commitment_x = 0x240;
+ uint256 private constant proof_grand_product_commitment_y = 0x260;
+
+ uint256 private constant proof_grand_product_at_zeta_omega = 0x280; // z(w*zeta)
+ uint256 private constant proof_quotient_polynomial_at_zeta = 0x2a0; // t(zeta)
+ uint256 private constant proof_linearised_polynomial_at_zeta = 0x2c0; // r(zeta)
+
+ // Folded proof for the opening of H, linearised poly, l, r, o, s_1, s_2, qcp
+ uint256 private constant proof_batch_opening_at_zeta_x = 0x2e0; // [Wzeta]
+ uint256 private constant proof_batch_opening_at_zeta_y = 0x300;
+
+ //Bn254.G1Point opening_at_zeta_omega_proof; // [Wzeta*omega]
+ uint256 private constant proof_opening_at_zeta_omega_x = 0x320;
+ uint256 private constant proof_opening_at_zeta_omega_y = 0x340;
+
+ uint256 private constant proof_openings_selector_commit_api_at_zeta = 0x360;
+ // -> next part of proof is
+ // [ openings_selector_commits || commitments_wires_commit_api]
+
+ // -------- offset state
+
+ // challenges to check the claimed quotient
+ uint256 private constant state_alpha = 0x00;
+ uint256 private constant state_beta = 0x20;
+ uint256 private constant state_gamma = 0x40;
+ uint256 private constant state_zeta = 0x60;
+
+ // challenges related to KZG
+ uint256 private constant state_sv = 0x80;
+ uint256 private constant state_su = 0xa0;
+
+ // reusable value
+ uint256 private constant state_alpha_square_lagrange = 0xc0;
+
+ // commitment to H
+ // Bn254.G1Point folded_h;
+ uint256 private constant state_folded_h_x = 0xe0;
+ uint256 private constant state_folded_h_y = 0x100;
+
+ // commitment to the linearised polynomial
+ uint256 private constant state_linearised_polynomial_x = 0x120;
+ uint256 private constant state_linearised_polynomial_y = 0x140;
+
+ // Folded proof for the opening of H, linearised poly, l, r, o, s_1, s_2, qcp
+ // Kzg.OpeningProof folded_proof;
+ uint256 private constant state_folded_claimed_values = 0x160;
+
+ // folded digests of H, linearised poly, l, r, o, s_1, s_2, qcp
+ // Bn254.G1Point folded_digests;
+ uint256 private constant state_folded_digests_x = 0x180;
+ uint256 private constant state_folded_digests_y = 0x1a0;
+
+ uint256 private constant state_pi = 0x1c0;
+
+ uint256 private constant state_zeta_power_n_minus_one = 0x1e0;
+ uint256 private constant state_alpha_square_lagrange_one = 0x200;
+
+ uint256 private constant state_gamma_kzg = 0x220;
+
+ uint256 private constant state_success = 0x240;
+ uint256 private constant state_check_var = 0x260; // /!\ this slot is used for debugging only
+
+ uint256 private constant state_last_mem = 0x280;
+
+ event PrintUint256(uint256 a);
+
+ function derive_gamma_beta_alpha_zeta(
+ bytes memory proof,
+ uint256[] memory public_inputs
+ ) internal view returns (uint256, uint256, uint256, uint256) {
+ uint256 gamma;
+ uint256 beta;
+ uint256 alpha;
+ uint256 zeta;
+
+ assembly ("memory-safe") {
+ let mem := mload(0x40)
+
+ derive_gamma(proof, public_inputs)
+ gamma := mload(mem)
+
+ derive_beta(proof, gamma)
+ beta := mload(mem)
+
+ derive_alpha(proof, beta)
+ alpha := mload(mem)
+
+ derive_zeta(proof, alpha)
+ zeta := mload(mem)
+
+ gamma := mod(gamma, r_mod)
+ beta := mod(beta, r_mod)
+ alpha := mod(alpha, r_mod)
+ zeta := mod(zeta, r_mod)
+
+ function derive_gamma(aproof, pub_inputs) {
+ let mPtr := mload(0x40)
+
+ // gamma
+ mstore(mPtr, 0x67616d6d61) // "gamma"
+
+ mstore(add(mPtr, 0x20), vk_s1_com_x)
+ mstore(add(mPtr, 0x40), vk_s1_com_y)
+ mstore(add(mPtr, 0x60), vk_s2_com_x)
+ mstore(add(mPtr, 0x80), vk_s2_com_y)
+ mstore(add(mPtr, 0xa0), vk_s3_com_x)
+ mstore(add(mPtr, 0xc0), vk_s3_com_y)
+ mstore(add(mPtr, 0xe0), vk_ql_com_x)
+ mstore(add(mPtr, 0x100), vk_ql_com_y)
+ mstore(add(mPtr, 0x120), vk_qr_com_x)
+ mstore(add(mPtr, 0x140), vk_qr_com_y)
+ mstore(add(mPtr, 0x160), vk_qm_com_x)
+ mstore(add(mPtr, 0x180), vk_qm_com_y)
+ mstore(add(mPtr, 0x1a0), vk_qo_com_x)
+ mstore(add(mPtr, 0x1c0), vk_qo_com_y)
+ mstore(add(mPtr, 0x1e0), vk_qk_com_x)
+ mstore(add(mPtr, 0x200), vk_qk_com_y)
+
+ let pi := add(pub_inputs, 0x20)
+ let _mPtr := add(mPtr, 0x220)
+ for {
+ let i := 0
+ } lt(i, mload(pub_inputs)) {
+ i := add(i, 1)
+ } {
+ mstore(_mPtr, mload(pi))
+ pi := add(pi, 0x20)
+ _mPtr := add(_mPtr, 0x20)
+ }
+
+ let _proof := add(aproof, proof_openings_selector_commit_api_at_zeta)
+ _proof := add(_proof, mul(vk_nb_commitments_commit_api, 0x20))
+ for {
+ let i := 0
+ } lt(i, vk_nb_commitments_commit_api) {
+ i := add(i, 1)
+ } {
+ mstore(_mPtr, mload(_proof))
+ mstore(add(_mPtr, 0x20), mload(add(_proof, 0x20)))
+ _mPtr := add(_mPtr, 0x40)
+ _proof := add(_proof, 0x40)
+ }
+ // pop(staticcall(sub(gas(), 2000), 0x2, add(mPtr, 0x1b), 0x2a5, mPtr, 0x20)) //0x1b -> 000.."gamma"
+
+ mstore(_mPtr, mload(add(aproof, proof_l_com_x)))
+ mstore(add(_mPtr, 0x20), mload(add(aproof, proof_l_com_y)))
+ mstore(add(_mPtr, 0x40), mload(add(aproof, proof_r_com_x)))
+ mstore(add(_mPtr, 0x60), mload(add(aproof, proof_r_com_y)))
+ mstore(add(_mPtr, 0x80), mload(add(aproof, proof_o_com_x)))
+ mstore(add(_mPtr, 0xa0), mload(add(aproof, proof_o_com_y)))
+ // pop(staticcall(sub(gas(), 2000), 0x2, add(mPtr, 0x1b), 0x365, mPtr, 0x20)) //0x1b -> 000.."gamma"
+
+ let size := add(0x2c5, mul(mload(pub_inputs), 0x20)) // 0x2c5 = 22*32+5
+ size := add(size, mul(vk_nb_commitments_commit_api, 0x40))
+ pop(staticcall(sub(gas(), 2000), 0x2, add(mPtr, 0x1b), size, mPtr, 0x20)) //0x1b -> 000.."gamma"
+ }
+
+ function derive_beta(aproof, prev_challenge) {
+ let mPtr := mload(0x40)
+ // beta
+ mstore(mPtr, 0x62657461) // "beta"
+ mstore(add(mPtr, 0x20), prev_challenge)
+ pop(staticcall(sub(gas(), 2000), 0x2, add(mPtr, 0x1c), 0x24, mPtr, 0x20)) //0x1b -> 000.."gamma"
+ }
+
+ function derive_alpha(aproof, prev_challenge) {
+ let mPtr := mload(0x40)
+ // alpha
+ mstore(mPtr, 0x616C706861) // "alpha"
+ mstore(add(mPtr, 0x20), prev_challenge)
+ mstore(add(mPtr, 0x40), mload(add(aproof, proof_grand_product_commitment_x)))
+ mstore(add(mPtr, 0x60), mload(add(aproof, proof_grand_product_commitment_y)))
+ pop(staticcall(sub(gas(), 2000), 0x2, add(mPtr, 0x1b), 0x65, mPtr, 0x20)) //0x1b -> 000.."gamma"
+ }
+
+ function derive_zeta(aproof, prev_challenge) {
+ let mPtr := mload(0x40)
+ // zeta
+ mstore(mPtr, 0x7a657461) // "zeta"
+ mstore(add(mPtr, 0x20), prev_challenge)
+ mstore(add(mPtr, 0x40), mload(add(aproof, proof_h_0_x)))
+ mstore(add(mPtr, 0x60), mload(add(aproof, proof_h_0_y)))
+ mstore(add(mPtr, 0x80), mload(add(aproof, proof_h_1_x)))
+ mstore(add(mPtr, 0xa0), mload(add(aproof, proof_h_1_y)))
+ mstore(add(mPtr, 0xc0), mload(add(aproof, proof_h_2_x)))
+ mstore(add(mPtr, 0xe0), mload(add(aproof, proof_h_2_y)))
+ pop(staticcall(sub(gas(), 2000), 0x2, add(mPtr, 0x1c), 0xe4, mPtr, 0x20))
+ }
+ }
+
+ return (gamma, beta, alpha, zeta);
+ }
+
+ function load_wire_commitments_commit_api(uint256[] memory wire_commitments, bytes memory proof) internal pure {
+ assembly ("memory-safe") {
+ let w := add(wire_commitments, 0x20)
+ let p := add(proof, proof_openings_selector_commit_api_at_zeta)
+ p := add(p, mul(vk_nb_commitments_commit_api, 0x20))
+ for {
+ let i := 0
+ } lt(i, mul(vk_nb_commitments_commit_api, 2)) {
+ i := add(i, 1)
+ } {
+ mstore(w, mload(p))
+ w := add(w, 0x20)
+ p := add(p, 0x20)
+ mstore(w, mload(p))
+ w := add(w, 0x20)
+ p := add(p, 0x20)
+ }
+ }
+ }
+
+ function compute_ith_lagrange_at_z(uint256 zeta, uint256 i) internal view returns (uint256) {
+ uint256 res;
+ assembly ("memory-safe") {
+ // _n^_i [r]
+ function pow_local(x, e) -> result {
+ let mPtr := mload(0x40)
+ mstore(mPtr, 0x20)
+ mstore(add(mPtr, 0x20), 0x20)
+ mstore(add(mPtr, 0x40), 0x20)
+ mstore(add(mPtr, 0x60), x)
+ mstore(add(mPtr, 0x80), e)
+ mstore(add(mPtr, 0xa0), r_mod)
+ pop(staticcall(sub(gas(), 2000), 0x05, mPtr, 0xc0, 0x00, 0x20))
+ result := mload(0x00)
+ }
+
+ let w := pow_local(vk_omega, i) // w**i
+ i := addmod(zeta, sub(r_mod, w), r_mod) // z-w**i
+ zeta := pow_local(zeta, vk_domain_size) // z**n
+ zeta := addmod(zeta, sub(r_mod, 1), r_mod) // z**n-1
+ w := mulmod(w, vk_inv_domain_size, r_mod) // w**i/n
+ i := pow_local(i, sub(r_mod, 2)) // (z-w**i)**-1
+ w := mulmod(w, i, r_mod) // w**i/n*(z-w)**-1
+ res := mulmod(w, zeta, r_mod)
+ }
+
+ return res;
+ }
+
+ function compute_pi(
+ bytes memory proof,
+ uint256[] memory public_inputs,
+ uint256 zeta
+ ) internal view returns (uint256) {
+ // evaluation of Z=Xâŋâģš at Îļ
+ // uint256 zeta_power_n_minus_one = Fr.pow(zeta, vk_domain_size);
+ // zeta_power_n_minus_one = Fr.sub(zeta_power_n_minus_one, 1);
+ uint256 zeta_power_n_minus_one;
+
+ uint256 pi;
+
+ assembly ("memory-safe") {
+ sum_pi_wo_api_commit(add(public_inputs, 0x20), mload(public_inputs), zeta)
+ pi := mload(mload(0x40))
+
+ function sum_pi_wo_api_commit(ins, n, z) {
+ let li := mload(0x40)
+ batch_compute_lagranges_at_z(z, n, li)
+ let res := 0
+ let tmp := 0
+ for {
+ let i := 0
+ } lt(i, n) {
+ i := add(i, 1)
+ } {
+ tmp := mulmod(mload(li), mload(ins), r_mod)
+ res := addmod(res, tmp, r_mod)
+ li := add(li, 0x20)
+ ins := add(ins, 0x20)
+ }
+ mstore(mload(0x40), res)
+ }
+
+ // mPtr <- [L_0(z), .., L_{n-1}(z)]
+ function batch_compute_lagranges_at_z(z, n, mPtr) {
+ let zn := addmod(pow(z, vk_domain_size, mPtr), sub(r_mod, 1), r_mod)
+ zn := mulmod(zn, vk_inv_domain_size, r_mod)
+ let _w := 1
+ let _mPtr := mPtr
+ for {
+ let i := 0
+ } lt(i, n) {
+ i := add(i, 1)
+ } {
+ mstore(_mPtr, addmod(z, sub(r_mod, _w), r_mod))
+ _w := mulmod(_w, vk_omega, r_mod)
+ _mPtr := add(_mPtr, 0x20)
+ }
+ batch_invert(mPtr, n, _mPtr)
+ _mPtr := mPtr
+ _w := 1
+ for {
+ let i := 0
+ } lt(i, n) {
+ i := add(i, 1)
+ } {
+ mstore(_mPtr, mulmod(mulmod(mload(_mPtr), zn, r_mod), _w, r_mod))
+ _mPtr := add(_mPtr, 0x20)
+ _w := mulmod(_w, vk_omega, r_mod)
+ }
+ }
+
+ function batch_invert(ins, nb_ins, mPtr) {
+ mstore(mPtr, 1)
+ let offset := 0
+ for {
+ let i := 0
+ } lt(i, nb_ins) {
+ i := add(i, 1)
+ } {
+ let prev := mload(add(mPtr, offset))
+ let cur := mload(add(ins, offset))
+ cur := mulmod(prev, cur, r_mod)
+ offset := add(offset, 0x20)
+ mstore(add(mPtr, offset), cur)
+ }
+ ins := add(ins, sub(offset, 0x20))
+ mPtr := add(mPtr, offset)
+ let inv := pow(mload(mPtr), sub(r_mod, 2), add(mPtr, 0x20))
+ for {
+ let i := 0
+ } lt(i, nb_ins) {
+ i := add(i, 1)
+ } {
+ mPtr := sub(mPtr, 0x20)
+ let tmp := mload(ins)
+ let cur := mulmod(inv, mload(mPtr), r_mod)
+ mstore(ins, cur)
+ inv := mulmod(inv, tmp, r_mod)
+ ins := sub(ins, 0x20)
+ }
+ }
+
+ // res <- x^e mod r
+ function pow(x, e, mPtr) -> res {
+ mstore(mPtr, 0x20)
+ mstore(add(mPtr, 0x20), 0x20)
+ mstore(add(mPtr, 0x40), 0x20)
+ mstore(add(mPtr, 0x60), x)
+ mstore(add(mPtr, 0x80), e)
+ mstore(add(mPtr, 0xa0), r_mod)
+ pop(staticcall(sub(gas(), 2000), 0x05, mPtr, 0xc0, mPtr, 0x20))
+ res := mload(mPtr)
+ }
+
+ zeta_power_n_minus_one := pow(zeta, vk_domain_size, mload(0x40))
+ zeta_power_n_minus_one := addmod(zeta_power_n_minus_one, sub(r_mod, 1), r_mod)
+ }
+
+ uint256[] memory commitment_indices = new uint256[](vk_nb_commitments_commit_api);
+ load_vk_commitments_indices_commit_api(commitment_indices);
+
+ if (vk_nb_commitments_commit_api > 0) {
+ uint256[] memory wire_committed_commitments;
+ wire_committed_commitments = new uint256[](2 * vk_nb_commitments_commit_api);
+ load_wire_commitments_commit_api(wire_committed_commitments, proof);
+
+ for (uint256 i = 0; i < vk_nb_commitments_commit_api; i++) {
+ uint256 hash_res = Utils.hash_fr(wire_committed_commitments[2 * i], wire_committed_commitments[2 * i + 1]);
+ uint256 a = compute_ith_lagrange_at_z(zeta, commitment_indices[i] + public_inputs.length);
+ assembly ("memory-safe") {
+ a := mulmod(hash_res, a, r_mod)
+ pi := addmod(pi, a, r_mod)
+ }
+ }
+ }
+
+ return pi;
+ }
+
+ function Verify(bytes memory proof, uint256[] memory public_inputs) external view returns (bool) {
+ uint256 gamma;
+ uint256 beta;
+ uint256 alpha;
+ uint256 zeta;
+
+ (gamma, beta, alpha, zeta) = derive_gamma_beta_alpha_zeta(proof, public_inputs);
+
+ uint256 pi = compute_pi(proof, public_inputs, zeta);
+
+ uint256 check;
+
+ bool success = false;
+ // uint256 success;
+
+ assembly ("memory-safe") {
+ let mem := mload(0x40)
+ mstore(add(mem, state_alpha), alpha)
+ mstore(add(mem, state_gamma), gamma)
+ mstore(add(mem, state_zeta), zeta)
+ mstore(add(mem, state_beta), beta)
+ mstore(add(mem, state_pi), pi)
+
+ compute_alpha_square_lagrange()
+ verify_quotient_poly_eval_at_zeta(proof)
+ fold_h(proof)
+ compute_commitment_linearised_polynomial(proof)
+ compute_gamma_kzg(proof)
+ fold_state(proof)
+ batch_verify_multi_points(proof)
+
+ success := mload(add(mem, state_success))
+
+ check := mload(add(mem, state_check_var))
+
+ function compute_alpha_square_lagrange() {
+ let state := mload(0x40)
+ let mPtr := add(mload(0x40), state_last_mem)
+
+ // zeta**n - 1
+ let res := pow(mload(add(state, state_zeta)), vk_domain_size, mPtr)
+ res := addmod(res, sub(r_mod, 1), r_mod)
+ mstore(add(state, state_zeta_power_n_minus_one), res)
+
+ // let res := mload(add(state, state_zeta_power_n_minus_one))
+ let den := addmod(mload(add(state, state_zeta)), sub(r_mod, 1), r_mod)
+ den := pow(den, sub(r_mod, 2), mPtr)
+ den := mulmod(den, vk_inv_domain_size, r_mod)
+ res := mulmod(den, res, r_mod)
+
+ let l_alpha := mload(add(state, state_alpha))
+ res := mulmod(res, l_alpha, r_mod)
+ res := mulmod(res, l_alpha, r_mod)
+ mstore(add(state, state_alpha_square_lagrange), res)
+ }
+
+ function batch_verify_multi_points(aproof) {
+ let state := mload(0x40)
+ let mPtr := add(state, state_last_mem)
+
+ // here the random is not a challenge, hence no need to use Fiat Shamir, we just
+ // need an unpredictible result.
+ let random := mod(keccak256(state, 0x20), r_mod)
+
+ let folded_quotients := mPtr
+ mPtr := add(folded_quotients, 0x40)
+ mstore(folded_quotients, mload(add(aproof, proof_batch_opening_at_zeta_x)))
+ mstore(add(folded_quotients, 0x20), mload(add(aproof, proof_batch_opening_at_zeta_y)))
+ point_acc_mul(folded_quotients, add(aproof, proof_opening_at_zeta_omega_x), random, mPtr)
+
+ let folded_digests := add(state, state_folded_digests_x)
+ point_acc_mul(folded_digests, add(aproof, proof_grand_product_commitment_x), random, mPtr)
+
+ let folded_evals := add(state, state_folded_claimed_values)
+ fr_acc_mul(folded_evals, add(aproof, proof_grand_product_at_zeta_omega), random)
+
+ let folded_evals_commit := mPtr
+ mPtr := add(folded_evals_commit, 0x40)
+ mstore(folded_evals_commit, 0x1)
+ mstore(add(folded_evals_commit, 0x20), 0x2)
+ mstore(add(folded_evals_commit, 0x40), mload(folded_evals))
+ pop(staticcall(sub(gas(), 2000), 7, folded_evals_commit, 0x60, folded_evals_commit, 0x40))
+
+ let folded_evals_commit_y := add(folded_evals_commit, 0x20)
+ mstore(folded_evals_commit_y, sub(p_mod, mload(folded_evals_commit_y)))
+ point_add(folded_digests, folded_digests, folded_evals_commit, mPtr)
+
+ let folded_points_quotients := mPtr
+ mPtr := add(mPtr, 0x40)
+ point_mul(
+ folded_points_quotients,
+ add(aproof, proof_batch_opening_at_zeta_x),
+ mload(add(state, state_zeta)),
+ mPtr
+ )
+ let zeta_omega := mulmod(mload(add(state, state_zeta)), vk_omega, r_mod)
+ random := mulmod(random, zeta_omega, r_mod)
+ point_acc_mul(folded_points_quotients, add(aproof, proof_opening_at_zeta_omega_x), random, mPtr)
+
+ point_add(folded_digests, folded_digests, folded_points_quotients, mPtr)
+
+ let folded_quotients_y := add(folded_quotients, 0x20)
+ mstore(folded_quotients_y, sub(p_mod, mload(folded_quotients_y)))
+ mstore(add(state, state_check_var), mload(add(folded_quotients, 0x20)))
+
+ mstore(mPtr, mload(folded_digests))
+ mstore(add(mPtr, 0x20), mload(add(folded_digests, 0x20)))
+ mstore(add(mPtr, 0x40), g2_srs_0_x_0) // the 4 lines are the canonical G2 point on BN254
+ mstore(add(mPtr, 0x60), g2_srs_0_x_1)
+ mstore(add(mPtr, 0x80), g2_srs_0_y_0)
+ mstore(add(mPtr, 0xa0), g2_srs_0_y_1)
+ mstore(add(mPtr, 0xc0), mload(folded_quotients))
+ mstore(add(mPtr, 0xe0), mload(add(folded_quotients, 0x20)))
+ mstore(add(mPtr, 0x100), g2_srs_1_x_0)
+ mstore(add(mPtr, 0x120), g2_srs_1_x_1)
+ mstore(add(mPtr, 0x140), g2_srs_1_y_0)
+ mstore(add(mPtr, 0x160), g2_srs_1_y_1)
+ let l_success := staticcall(sub(gas(), 2000), 8, mPtr, 0x180, 0x00, 0x20)
+ // l_success := true
+ mstore(add(state, state_success), and(l_success, mload(add(state, state_success))))
+ // mstore(add(state, state_success), l_success)
+ // mstore(add(state, state_check_var), mload(mPtr))
+ }
+
+ // at this stage the state of mPtr is the same as in compute_gamma
+ function fold_state(aproof) {
+ let state := mload(0x40)
+ let mPtr := add(mload(0x40), state_last_mem)
+
+ let l_gamma_kzg := mload(add(state, state_gamma_kzg))
+ let acc_gamma := l_gamma_kzg
+
+ let offset := add(0x200, mul(vk_nb_commitments_commit_api, 0x40)) // 0x40 = 2*0x20
+ let mPtrOffset := add(mPtr, offset)
+
+ mstore(add(state, state_folded_digests_x), mload(add(mPtr, 0x40)))
+ mstore(add(state, state_folded_digests_y), mload(add(mPtr, 0x60)))
+ mstore(add(state, state_folded_claimed_values), mload(add(aproof, proof_quotient_polynomial_at_zeta)))
+
+ point_acc_mul(add(state, state_folded_digests_x), add(mPtr, 0x80), acc_gamma, mPtrOffset)
+ fr_acc_mul(add(state, state_folded_claimed_values), add(aproof, proof_linearised_polynomial_at_zeta), acc_gamma)
+ mstore(add(state, state_check_var), acc_gamma)
+
+ acc_gamma := mulmod(acc_gamma, l_gamma_kzg, r_mod)
+ point_acc_mul(add(state, state_folded_digests_x), add(mPtr, 0xc0), acc_gamma, mPtrOffset)
+ fr_acc_mul(add(state, state_folded_claimed_values), add(aproof, proof_l_at_zeta), acc_gamma)
+
+ acc_gamma := mulmod(acc_gamma, l_gamma_kzg, r_mod)
+ point_acc_mul(add(state, state_folded_digests_x), add(mPtr, 0x100), acc_gamma, add(mPtr, offset))
+ fr_acc_mul(add(state, state_folded_claimed_values), add(aproof, proof_r_at_zeta), acc_gamma)
+
+ acc_gamma := mulmod(acc_gamma, l_gamma_kzg, r_mod)
+ point_acc_mul(add(state, state_folded_digests_x), add(mPtr, 0x140), acc_gamma, add(mPtr, offset))
+ fr_acc_mul(add(state, state_folded_claimed_values), add(aproof, proof_o_at_zeta), acc_gamma)
+
+ acc_gamma := mulmod(acc_gamma, l_gamma_kzg, r_mod)
+ point_acc_mul(add(state, state_folded_digests_x), add(mPtr, 0x180), acc_gamma, add(mPtr, offset))
+ fr_acc_mul(add(state, state_folded_claimed_values), add(aproof, proof_s1_at_zeta), acc_gamma)
+
+ acc_gamma := mulmod(acc_gamma, l_gamma_kzg, r_mod)
+ point_acc_mul(add(state, state_folded_digests_x), add(mPtr, 0x1c0), acc_gamma, add(mPtr, offset))
+ fr_acc_mul(add(state, state_folded_claimed_values), add(aproof, proof_s2_at_zeta), acc_gamma)
+
+ let poscaz := add(aproof, proof_openings_selector_commit_api_at_zeta)
+ let opca := add(mPtr, 0x200) // offset_proof_commits_api
+ for {
+ let i := 0
+ } lt(i, vk_nb_commitments_commit_api) {
+ i := add(i, 1)
+ } {
+ acc_gamma := mulmod(acc_gamma, l_gamma_kzg, r_mod)
+ point_acc_mul(add(state, state_folded_digests_x), opca, acc_gamma, add(mPtr, offset))
+ fr_acc_mul(add(state, state_folded_claimed_values), poscaz, acc_gamma)
+ poscaz := add(poscaz, 0x20)
+ opca := add(opca, 0x40)
+ }
+ }
+
+ function compute_gamma_kzg(aproof) {
+ let state := mload(0x40)
+ let mPtr := add(mload(0x40), state_last_mem)
+ mstore(mPtr, 0x67616d6d61) // "gamma"
+ mstore(add(mPtr, 0x20), mload(add(state, state_zeta)))
+ mstore(add(mPtr, 0x40), mload(add(state, state_folded_h_x)))
+ mstore(add(mPtr, 0x60), mload(add(state, state_folded_h_y)))
+ mstore(add(mPtr, 0x80), mload(add(state, state_linearised_polynomial_x)))
+ mstore(add(mPtr, 0xa0), mload(add(state, state_linearised_polynomial_y)))
+ mstore(add(mPtr, 0xc0), mload(add(aproof, proof_l_com_x)))
+ mstore(add(mPtr, 0xe0), mload(add(aproof, proof_l_com_y)))
+ mstore(add(mPtr, 0x100), mload(add(aproof, proof_r_com_x)))
+ mstore(add(mPtr, 0x120), mload(add(aproof, proof_r_com_y)))
+ mstore(add(mPtr, 0x140), mload(add(aproof, proof_o_com_x)))
+ mstore(add(mPtr, 0x160), mload(add(aproof, proof_o_com_y)))
+ mstore(add(mPtr, 0x180), vk_s1_com_x)
+ mstore(add(mPtr, 0x1a0), vk_s1_com_y)
+ mstore(add(mPtr, 0x1c0), vk_s2_com_x)
+ mstore(add(mPtr, 0x1e0), vk_s2_com_y)
+
+ let offset := 0x200
+ mstore(add(mPtr, offset), vk_selector_commitments_commit_api_0_x)
+ mstore(add(mPtr, add(offset, 0x20)), vk_selector_commitments_commit_api_0_y)
+ offset := add(offset, 0x40)
+
+ mstore(add(mPtr, offset), mload(add(aproof, proof_quotient_polynomial_at_zeta)))
+ mstore(add(mPtr, add(offset, 0x20)), mload(add(aproof, proof_linearised_polynomial_at_zeta)))
+ mstore(add(mPtr, add(offset, 0x40)), mload(add(aproof, proof_l_at_zeta)))
+ mstore(add(mPtr, add(offset, 0x60)), mload(add(aproof, proof_r_at_zeta)))
+ mstore(add(mPtr, add(offset, 0x80)), mload(add(aproof, proof_o_at_zeta)))
+ mstore(add(mPtr, add(offset, 0xa0)), mload(add(aproof, proof_s1_at_zeta)))
+ mstore(add(mPtr, add(offset, 0xc0)), mload(add(aproof, proof_s2_at_zeta)))
+
+ let _mPtr := add(mPtr, add(offset, 0xe0))
+ let _poscaz := add(aproof, proof_openings_selector_commit_api_at_zeta)
+ for {
+ let i := 0
+ } lt(i, vk_nb_commitments_commit_api) {
+ i := add(i, 1)
+ } {
+ mstore(_mPtr, mload(_poscaz))
+ _poscaz := add(_poscaz, 0x20)
+ _mPtr := add(_mPtr, 0x20)
+ }
+
+ let start_input := 0x1b // 00.."gamma"
+ let size_input := add(0x16, mul(vk_nb_commitments_commit_api, 3)) // number of 32bytes elmts = 0x16 (zeta+2*7+7 for the digests+openings) + 2*vk_nb_commitments_commit_api (for the commitments of the selectors) + vk_nb_commitments_commit_api (for the openings of the selectors)
+ size_input := add(0x5, mul(size_input, 0x20)) // size in bytes: 15*32 bytes + 5 bytes for gamma
+ pop(staticcall(sub(gas(), 2000), 0x2, add(mPtr, start_input), size_input, add(state, state_gamma_kzg), 0x20))
+ mstore(add(state, state_gamma_kzg), mod(mload(add(state, state_gamma_kzg)), r_mod))
+ }
+
+ function compute_commitment_linearised_polynomial_ec(aproof, s1, s2) {
+ let state := mload(0x40)
+ let mPtr := add(mload(0x40), state_last_mem)
+
+ mstore(mPtr, vk_ql_com_x)
+ mstore(add(mPtr, 0x20), vk_ql_com_y)
+ point_mul(add(state, state_linearised_polynomial_x), mPtr, mload(add(aproof, proof_l_at_zeta)), add(mPtr, 0x40))
+
+ mstore(mPtr, vk_qr_com_x)
+ mstore(add(mPtr, 0x20), vk_qr_com_y)
+ point_acc_mul(
+ add(state, state_linearised_polynomial_x),
+ mPtr,
+ mload(add(aproof, proof_r_at_zeta)),
+ add(mPtr, 0x40)
+ )
+
+ let rl := mulmod(mload(add(aproof, proof_l_at_zeta)), mload(add(aproof, proof_r_at_zeta)), r_mod)
+ mstore(mPtr, vk_qm_com_x)
+ mstore(add(mPtr, 0x20), vk_qm_com_y)
+ point_acc_mul(add(state, state_linearised_polynomial_x), mPtr, rl, add(mPtr, 0x40))
+
+ mstore(mPtr, vk_qo_com_x)
+ mstore(add(mPtr, 0x20), vk_qo_com_y)
+ point_acc_mul(
+ add(state, state_linearised_polynomial_x),
+ mPtr,
+ mload(add(aproof, proof_o_at_zeta)),
+ add(mPtr, 0x40)
+ )
+
+ mstore(mPtr, vk_qk_com_x)
+ mstore(add(mPtr, 0x20), vk_qk_com_y)
+ point_add(
+ add(state, state_linearised_polynomial_x),
+ add(state, state_linearised_polynomial_x),
+ mPtr,
+ add(mPtr, 0x40)
+ )
+
+ let commits_api_at_zeta := add(aproof, proof_openings_selector_commit_api_at_zeta)
+ let commits_api := add(
+ aproof,
+ add(proof_openings_selector_commit_api_at_zeta, mul(vk_nb_commitments_commit_api, 0x20))
+ )
+ for {
+ let i := 0
+ } lt(i, vk_nb_commitments_commit_api) {
+ i := add(i, 1)
+ } {
+ mstore(mPtr, mload(commits_api))
+ mstore(add(mPtr, 0x20), mload(add(commits_api, 0x20)))
+ point_acc_mul(add(state, state_linearised_polynomial_x), mPtr, mload(commits_api_at_zeta), add(mPtr, 0x40))
+ commits_api_at_zeta := add(commits_api_at_zeta, 0x20)
+ commits_api := add(commits_api, 0x40)
+ }
+
+ mstore(mPtr, vk_s3_com_x)
+ mstore(add(mPtr, 0x20), vk_s3_com_y)
+ point_acc_mul(add(state, state_linearised_polynomial_x), mPtr, s1, add(mPtr, 0x40))
+
+ mstore(mPtr, mload(add(aproof, proof_grand_product_commitment_x)))
+ mstore(add(mPtr, 0x20), mload(add(aproof, proof_grand_product_commitment_y)))
+ point_acc_mul(add(state, state_linearised_polynomial_x), mPtr, s2, add(mPtr, 0x40))
+ }
+
+ function compute_commitment_linearised_polynomial(aproof) {
+ let state := mload(0x40)
+ let l_beta := mload(add(state, state_beta))
+ let l_gamma := mload(add(state, state_gamma))
+ let l_zeta := mload(add(state, state_zeta))
+ let l_alpha := mload(add(state, state_alpha))
+
+ let u := mulmod(mload(add(aproof, proof_grand_product_at_zeta_omega)), l_beta, r_mod)
+ let v := mulmod(l_beta, mload(add(aproof, proof_s1_at_zeta)), r_mod)
+ v := addmod(v, mload(add(aproof, proof_l_at_zeta)), r_mod)
+ v := addmod(v, l_gamma, r_mod)
+
+ let w := mulmod(l_beta, mload(add(aproof, proof_s2_at_zeta)), r_mod)
+ w := addmod(w, mload(add(aproof, proof_r_at_zeta)), r_mod)
+ w := addmod(w, l_gamma, r_mod)
+
+ let s1 := mulmod(u, v, r_mod)
+ s1 := mulmod(s1, w, r_mod)
+ s1 := mulmod(s1, l_alpha, r_mod)
+
+ let coset_square := mulmod(vk_coset_shift, vk_coset_shift, r_mod)
+ let betazeta := mulmod(l_beta, l_zeta, r_mod)
+ u := addmod(betazeta, mload(add(aproof, proof_l_at_zeta)), r_mod)
+ u := addmod(u, l_gamma, r_mod)
+
+ v := mulmod(betazeta, vk_coset_shift, r_mod)
+ v := addmod(v, mload(add(aproof, proof_r_at_zeta)), r_mod)
+ v := addmod(v, l_gamma, r_mod)
+
+ w := mulmod(betazeta, coset_square, r_mod)
+ w := addmod(w, mload(add(aproof, proof_o_at_zeta)), r_mod)
+ w := addmod(w, l_gamma, r_mod)
+
+ let s2 := mulmod(u, v, r_mod)
+ s2 := mulmod(s2, w, r_mod)
+ s2 := sub(r_mod, s2)
+ s2 := mulmod(s2, l_alpha, r_mod)
+ s2 := addmod(s2, mload(add(state, state_alpha_square_lagrange)), r_mod)
+
+ compute_commitment_linearised_polynomial_ec(aproof, s1, s2)
+ }
+
+ function fold_h(aproof) {
+ let state := mload(0x40)
+ let n_plus_two := add(vk_domain_size, 2)
+ let mPtr := add(mload(0x40), state_last_mem)
+ let zeta_power_n_plus_two := pow(mload(add(state, state_zeta)), n_plus_two, mPtr)
+ point_mul(add(state, state_folded_h_x), add(aproof, proof_h_2_x), zeta_power_n_plus_two, mPtr)
+ point_add(add(state, state_folded_h_x), add(state, state_folded_h_x), add(aproof, proof_h_1_x), mPtr)
+ point_mul(add(state, state_folded_h_x), add(state, state_folded_h_x), zeta_power_n_plus_two, mPtr)
+ point_add(add(state, state_folded_h_x), add(state, state_folded_h_x), add(aproof, proof_h_0_x), mPtr)
+ }
+
+ function verify_quotient_poly_eval_at_zeta(aproof) {
+ let state := mload(0x40)
+
+ // (l(Îļ)+β*s1(Îļ)+Îŗ)
+ let s1 := add(mload(0x40), state_last_mem)
+ mstore(s1, mulmod(mload(add(aproof, proof_s1_at_zeta)), mload(add(state, state_beta)), r_mod))
+ mstore(s1, addmod(mload(s1), mload(add(state, state_gamma)), r_mod))
+ mstore(s1, addmod(mload(s1), mload(add(aproof, proof_l_at_zeta)), r_mod))
+
+ // (r(Îļ)+β*s2(Îļ)+Îŗ)
+ let s2 := add(s1, 0x20)
+ mstore(s2, mulmod(mload(add(aproof, proof_s2_at_zeta)), mload(add(state, state_beta)), r_mod))
+ mstore(s2, addmod(mload(s2), mload(add(state, state_gamma)), r_mod))
+ mstore(s2, addmod(mload(s2), mload(add(aproof, proof_r_at_zeta)), r_mod))
+ // _s2 := mload(s2)
+
+ // (o(Îļ)+Îŗ)
+ let o := add(s1, 0x40)
+ mstore(o, addmod(mload(add(aproof, proof_o_at_zeta)), mload(add(state, state_gamma)), r_mod))
+
+ // Îą*(Z(ÎŧÎļ))*(l(Îļ)+β*s1(Îļ)+Îŗ)*(r(Îļ)+β*s2(Îļ)+Îŗ)*(o(Îļ)+Îŗ)
+ mstore(s1, mulmod(mload(s1), mload(s2), r_mod))
+ mstore(s1, mulmod(mload(s1), mload(o), r_mod))
+ mstore(s1, mulmod(mload(s1), mload(add(state, state_alpha)), r_mod))
+ mstore(s1, mulmod(mload(s1), mload(add(aproof, proof_grand_product_at_zeta_omega)), r_mod))
+
+ let computed_quotient := add(s1, 0x60)
+
+ // linearizedpolynomial + pi(zeta)
+ mstore(
+ computed_quotient,
+ addmod(mload(add(aproof, proof_linearised_polynomial_at_zeta)), mload(add(state, state_pi)), r_mod)
+ )
+ mstore(computed_quotient, addmod(mload(computed_quotient), mload(s1), r_mod))
+ mstore(
+ computed_quotient,
+ addmod(mload(computed_quotient), sub(r_mod, mload(add(state, state_alpha_square_lagrange))), r_mod)
+ )
+ mstore(
+ s2,
+ mulmod(
+ mload(add(aproof, proof_quotient_polynomial_at_zeta)),
+ mload(add(state, state_zeta_power_n_minus_one)),
+ r_mod
+ )
+ )
+ mstore(add(state, state_success), mload(computed_quotient))
+
+ mstore(add(state, state_success), eq(mload(computed_quotient), mload(s2)))
+ }
+
+ function point_add(dst, p, q, mPtr) {
+ // let mPtr := add(mload(0x40), state_last_mem)
+ let state := mload(0x40)
+ mstore(mPtr, mload(p))
+ mstore(add(mPtr, 0x20), mload(add(p, 0x20)))
+ mstore(add(mPtr, 0x40), mload(q))
+ mstore(add(mPtr, 0x60), mload(add(q, 0x20)))
+ let l_success := staticcall(sub(gas(), 2000), 6, mPtr, 0x80, dst, 0x40)
+ mstore(add(state, state_success), and(l_success, mload(add(state, state_success))))
+ }
+
+ // dst <- [s]src
+ function point_mul(dst, src, s, mPtr) {
+ // let mPtr := add(mload(0x40), state_last_mem)
+ let state := mload(0x40)
+ mstore(mPtr, mload(src))
+ mstore(add(mPtr, 0x20), mload(add(src, 0x20)))
+ mstore(add(mPtr, 0x40), s)
+ let l_success := staticcall(sub(gas(), 2000), 7, mPtr, 0x60, dst, 0x40)
+ mstore(add(state, state_success), and(l_success, mload(add(state, state_success))))
+ }
+
+ // dst <- dst + [s]src (Elliptic curve)
+ function point_acc_mul(dst, src, s, mPtr) {
+ let state := mload(0x40)
+ mstore(mPtr, mload(src))
+ mstore(add(mPtr, 0x20), mload(add(src, 0x20)))
+ mstore(add(mPtr, 0x40), s)
+ let l_success := staticcall(sub(gas(), 2000), 7, mPtr, 0x60, mPtr, 0x40)
+ mstore(add(mPtr, 0x40), mload(dst))
+ mstore(add(mPtr, 0x60), mload(add(dst, 0x20)))
+ l_success := and(l_success, staticcall(sub(gas(), 2000), 6, mPtr, 0x80, dst, 0x40))
+ mstore(add(state, state_success), and(l_success, mload(add(state, state_success))))
+ }
+
+ // dst <- dst + src (Fr) dst,src are addresses, s is a value
+ function fr_acc_mul(dst, src, s) {
+ let tmp := mulmod(mload(src), s, r_mod)
+ mstore(dst, addmod(mload(dst), tmp, r_mod))
+ }
+
+ // dst <- x ** e mod r (x, e are values, not pointers)
+ function pow(x, e, mPtr) -> res {
+ mstore(mPtr, 0x20)
+ mstore(add(mPtr, 0x20), 0x20)
+ mstore(add(mPtr, 0x40), 0x20)
+ mstore(add(mPtr, 0x60), x)
+ mstore(add(mPtr, 0x80), e)
+ mstore(add(mPtr, 0xa0), r_mod)
+ pop(staticcall(sub(gas(), 2000), 0x05, mPtr, 0xc0, mPtr, 0x20))
+ res := mload(mPtr)
+ }
+ }
+ // success = true;
+ // if (success ==true){
+ // emit PrintUint256(1);
+ // }
+ // if (success==false){
+ // emit PrintUint256(0);
+ // }
+
+ return success;
+ }
+}
diff --git a/contracts/contracts/verifiers/PlonkVerifierFull.sol b/contracts/contracts/verifiers/PlonkVerifierFull.sol
new file mode 100644
index 000000000..717d478fc
--- /dev/null
+++ b/contracts/contracts/verifiers/PlonkVerifierFull.sol
@@ -0,0 +1,1223 @@
+// SPDX-License-Identifier: Apache-2.0
+
+// Copyright 2023 Consensys Software Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Code generated by gnark DO NOT EDIT
+
+pragma solidity 0.8.19;
+
+contract PlonkVerifierFull {
+ uint256 private constant r_mod = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
+ uint256 private constant p_mod = 21888242871839275222246405745257275088696311157297823662689037894645226208583;
+
+ uint256 private constant g2_srs_0_x_0 = 11559732032986387107991004021392285783925812861821192530917403151452391805634;
+ uint256 private constant g2_srs_0_x_1 = 10857046999023057135944570762232829481370756359578518086990519993285655852781;
+ uint256 private constant g2_srs_0_y_0 = 4082367875863433681332203403145435568316851327593401208105741076214120093531;
+ uint256 private constant g2_srs_0_y_1 = 8495653923123431417604973247489272438418190587263600148770280649306958101930;
+
+ uint256 private constant g2_srs_1_x_0 = 15805639136721018565402881920352193254830339253282065586954346329754995870280;
+ uint256 private constant g2_srs_1_x_1 = 19089565590083334368588890253123139704298730990782503769911324779715431555531;
+ uint256 private constant g2_srs_1_y_0 = 9779648407879205346559610309258181044130619080926897934572699915909528404984;
+ uint256 private constant g2_srs_1_y_1 = 6779728121489434657638426458390319301070371227460768374343986326751507916979;
+
+ // ----------------------- vk ---------------------
+ uint256 private constant vk_domain_size = 33554432;
+ uint256 private constant vk_inv_domain_size =
+ 21888242219518804655518433051623070663413851959604507555939307129453691614729;
+ uint256 private constant vk_omega = 19200870435978225707111062059747084165650991997241425080699860725083300967194;
+ uint256 private constant vk_ql_com_x = 15219271245698983703471423648023665720996205681784744600640314563625154579786;
+ uint256 private constant vk_ql_com_y = 20147594135982735259732754468148273541964923282562024733532785339023994968013;
+ uint256 private constant vk_qr_com_x = 21471071871292076580302623340312052053656709918995831984303212428778960332599;
+ uint256 private constant vk_qr_com_y = 15259265517764171942718107061584834645622370551725116555489996695545525645011;
+ uint256 private constant vk_qm_com_x = 7348167581357627394271400666076873505823385867737608431101258472693436797940;
+ uint256 private constant vk_qm_com_y = 4441183888936334562772144285824576613397978465178907198241592714557417319270;
+ uint256 private constant vk_qo_com_x = 690118329970351488385912633539015955126172086565412016261383595846817534429;
+ uint256 private constant vk_qo_com_y = 21854047320402578077195589346598189991187569103145043066685944954863560120820;
+ uint256 private constant vk_qk_com_x = 13603163727786566911398797609626301727351105723694955263993678345045934228870;
+ uint256 private constant vk_qk_com_y = 8339979733962310806038498361419393444965826606900970819477237490233428996333;
+
+ uint256 private constant vk_s1_com_x = 634493952854013755921699384519724653689756821082556819717184748995785102434;
+ uint256 private constant vk_s1_com_y = 20558435202691230255449033800343112788829920102758977812256470547750228131634;
+
+ uint256 private constant vk_s2_com_x = 6249349279481794121010728521332464829934723701621873491125551712685998783470;
+ uint256 private constant vk_s2_com_y = 4768324564744950463017317507840427044475111606201230109107364345742145486572;
+
+ uint256 private constant vk_s3_com_x = 6211604418720336838649890166502267316070740192872385963594274693381591578311;
+ uint256 private constant vk_s3_com_y = 3950283788807144685755254392627057902299881176666195233354776073965155557919;
+
+ uint256 private constant vk_coset_shift = 5;
+
+ uint256 private constant vk_selector_commitments_commit_api_0_x =
+ 2614573220337297659179308133300379021102641010525403337401619021428140031269;
+ uint256 private constant vk_selector_commitments_commit_api_0_y =
+ 5896590631125620550976365652082599923038691774487942725877415439318691171350;
+
+ uint256 private constant vk_index_commit_api_0 = 16790349;
+
+ uint256 private constant vk_nb_commitments_commit_api = 1;
+
+ // ------------------------------------------------
+
+ // offset proof
+ uint256 private constant proof_l_com_x = 0x00;
+ uint256 private constant proof_l_com_y = 0x20;
+ uint256 private constant proof_r_com_x = 0x40;
+ uint256 private constant proof_r_com_y = 0x60;
+ uint256 private constant proof_o_com_x = 0x80;
+ uint256 private constant proof_o_com_y = 0xa0;
+
+ // h = h_0 + x^{n+2}h_1 + x^{2(n+2)}h_2
+ uint256 private constant proof_h_0_x = 0xc0;
+ uint256 private constant proof_h_0_y = 0xe0;
+ uint256 private constant proof_h_1_x = 0x100;
+ uint256 private constant proof_h_1_y = 0x120;
+ uint256 private constant proof_h_2_x = 0x140;
+ uint256 private constant proof_h_2_y = 0x160;
+
+ // wire values at zeta
+ uint256 private constant proof_l_at_zeta = 0x180;
+ uint256 private constant proof_r_at_zeta = 0x1a0;
+ uint256 private constant proof_o_at_zeta = 0x1c0;
+
+ //uint256[STATE_WIDTH-1] permutation_polynomials_at_zeta; // SĪ1(zeta),SĪ2(zeta)
+ uint256 private constant proof_s1_at_zeta = 0x1e0; // SĪ1(zeta)
+ uint256 private constant proof_s2_at_zeta = 0x200; // SĪ2(zeta)
+
+ //Bn254.G1Point grand_product_commitment; // [z(x)]
+ uint256 private constant proof_grand_product_commitment_x = 0x220;
+ uint256 private constant proof_grand_product_commitment_y = 0x240;
+
+ uint256 private constant proof_grand_product_at_zeta_omega = 0x260; // z(w*zeta)
+ uint256 private constant proof_quotient_polynomial_at_zeta = 0x280; // t(zeta)
+ uint256 private constant proof_linearised_polynomial_at_zeta = 0x2a0; // r(zeta)
+
+ // Folded proof for the opening of H, linearised poly, l, r, o, s_1, s_2, qcp
+ uint256 private constant proof_batch_opening_at_zeta_x = 0x2c0; // [Wzeta]
+ uint256 private constant proof_batch_opening_at_zeta_y = 0x2e0;
+
+ //Bn254.G1Point opening_at_zeta_omega_proof; // [Wzeta*omega]
+ uint256 private constant proof_opening_at_zeta_omega_x = 0x300;
+ uint256 private constant proof_opening_at_zeta_omega_y = 0x320;
+
+ uint256 private constant proof_openings_selector_commit_api_at_zeta = 0x340;
+ // -> next part of proof is
+ // [ openings_selector_commits || commitments_wires_commit_api]
+
+ // -------- offset state
+
+ // challenges to check the claimed quotient
+ uint256 private constant state_alpha = 0x00;
+ uint256 private constant state_beta = 0x20;
+ uint256 private constant state_gamma = 0x40;
+ uint256 private constant state_zeta = 0x60;
+
+ // reusable value
+ uint256 private constant state_alpha_square_lagrange_0 = 0x80;
+
+ // commitment to H
+ uint256 private constant state_folded_h_x = 0xa0;
+ uint256 private constant state_folded_h_y = 0xc0;
+
+ // commitment to the linearised polynomial
+ uint256 private constant state_linearised_polynomial_x = 0xe0;
+ uint256 private constant state_linearised_polynomial_y = 0x100;
+
+ // Folded proof for the opening of H, linearised poly, l, r, o, s_1, s_2, qcp
+ uint256 private constant state_folded_claimed_values = 0x120;
+
+ // folded digests of H, linearised poly, l, r, o, s_1, s_2, qcp
+ // Bn254.G1Point folded_digests;
+ uint256 private constant state_folded_digests_x = 0x140;
+ uint256 private constant state_folded_digests_y = 0x160;
+
+ uint256 private constant state_pi = 0x180;
+
+ uint256 private constant state_zeta_power_n_minus_one = 0x1a0;
+
+ uint256 private constant state_gamma_kzg = 0x1c0;
+
+ uint256 private constant state_success = 0x1e0;
+ uint256 private constant state_check_var = 0x200; // /!\ this slot is used for debugging only
+
+ uint256 private constant state_last_mem = 0x220;
+
+ // -------- errors
+ uint256 private constant error_string_id = 0x08c379a000000000000000000000000000000000000000000000000000000000; // selector for function Error(string)
+
+ // -------- utils (for hash_fr)
+ uint256 private constant bb = 340282366920938463463374607431768211456; // 2**128
+ uint256 private constant zero_uint256 = 0;
+
+ uint8 private constant lenInBytes = 48;
+ uint8 private constant sizeDomain = 11;
+ uint8 private constant one = 1;
+ uint8 private constant two = 2;
+
+ function Verify(bytes calldata proof, uint256[] calldata public_inputs) public view returns (bool success) {
+ assembly {
+ let mem := mload(0x40)
+ let freeMem := add(mem, state_last_mem)
+
+ // sanity checks
+ check_inputs_size(public_inputs.length, public_inputs.offset)
+ check_proof_size(proof.length)
+ check_proof_openings_size(proof.offset)
+
+ // compute the challenges
+ let prev_challenge_non_reduced
+ prev_challenge_non_reduced := derive_gamma(proof.offset, public_inputs.length, public_inputs.offset)
+ prev_challenge_non_reduced := derive_beta(prev_challenge_non_reduced)
+ prev_challenge_non_reduced := derive_alpha(proof.offset, prev_challenge_non_reduced)
+ derive_zeta(proof.offset, prev_challenge_non_reduced)
+
+ // evaluation of Z=Xâŋ-1 at Îļ, we save this value
+ let zeta := mload(add(mem, state_zeta))
+ let zeta_power_n_minus_one := addmod(pow(zeta, vk_domain_size, freeMem), sub(r_mod, 1), r_mod)
+ mstore(add(mem, state_zeta_power_n_minus_one), zeta_power_n_minus_one)
+
+ // public inputs contribution
+ let l_pi := sum_pi_wo_api_commit(public_inputs.offset, public_inputs.length, freeMem)
+ let l_wocommit := sum_pi_commit(proof.offset, public_inputs.length, freeMem)
+ l_pi := addmod(l_wocommit, l_pi, r_mod)
+ mstore(add(mem, state_pi), l_pi)
+
+ compute_alpha_square_lagrange_0()
+ verify_quotient_poly_eval_at_zeta(proof.offset)
+ fold_h(proof.offset)
+ compute_commitment_linearised_polynomial(proof.offset)
+ compute_gamma_kzg(proof.offset)
+ fold_state(proof.offset)
+ batch_verify_multi_points(proof.offset)
+
+ success := mload(add(mem, state_success))
+
+ // Beginning errors -------------------------------------------------
+ function error_ec_op() {
+ let ptError := mload(0x40)
+ mstore(ptError, error_string_id) // selector for function Error(string)
+ mstore(add(ptError, 0x4), 0x20)
+ mstore(add(ptError, 0x24), 0x12)
+ mstore(add(ptError, 0x44), "error ec operation")
+ revert(ptError, 0x64)
+ }
+
+ function error_inputs_size() {
+ let ptError := mload(0x40)
+ mstore(ptError, error_string_id) // selector for function Error(string)
+ mstore(add(ptError, 0x4), 0x20)
+ mstore(add(ptError, 0x24), 0x18)
+ mstore(add(ptError, 0x44), "inputs are bigger than r")
+ revert(ptError, 0x64)
+ }
+
+ function error_proof_size() {
+ let ptError := mload(0x40)
+ mstore(ptError, error_string_id) // selector for function Error(string)
+ mstore(add(ptError, 0x4), 0x20)
+ mstore(add(ptError, 0x24), 0x10)
+ mstore(add(ptError, 0x44), "wrong proof size")
+ revert(ptError, 0x64)
+ }
+
+ function error_proof_openings_size() {
+ let ptError := mload(0x40)
+ mstore(ptError, error_string_id) // selector for function Error(string)
+ mstore(add(ptError, 0x4), 0x20)
+ mstore(add(ptError, 0x24), 0x16)
+ mstore(add(ptError, 0x44), "openings bigger than r")
+ revert(ptError, 0x64)
+ }
+
+ function error_verify() {
+ let ptError := mload(0x40)
+ mstore(ptError, error_string_id) // selector for function Error(string)
+ mstore(add(ptError, 0x4), 0x20)
+ mstore(add(ptError, 0x24), 0xc)
+ mstore(add(ptError, 0x44), "error verify")
+ revert(ptError, 0x64)
+ }
+ // end errors -------------------------------------------------
+
+ // Beginning checks -------------------------------------------------
+
+ // s number of public inputs, p pointer the public inputs
+ function check_inputs_size(s, p) {
+ let input_checks := 1
+ for {
+ let i
+ } lt(i, s) {
+ i := add(i, 1)
+ } {
+ input_checks := and(input_checks, lt(calldataload(p), r_mod))
+ p := add(p, 0x20)
+ }
+ if iszero(input_checks) {
+ error_inputs_size()
+ }
+ }
+
+ function check_proof_size(actual_proof_size) {
+ let expected_proof_size := add(0x340, mul(vk_nb_commitments_commit_api, 0x60))
+ if iszero(eq(actual_proof_size, expected_proof_size)) {
+ error_proof_size()
+ }
+ }
+
+ function check_proof_openings_size(aproof) {
+ let openings_check := 1
+
+ // linearised polynomial at zeta
+ let p := add(aproof, proof_linearised_polynomial_at_zeta)
+ openings_check := and(openings_check, lt(calldataload(p), r_mod))
+
+ // quotient polynomial at zeta
+ p := add(aproof, proof_quotient_polynomial_at_zeta)
+ openings_check := and(openings_check, lt(calldataload(p), r_mod))
+
+ // proof_l_at_zeta
+ p := add(aproof, proof_l_at_zeta)
+ openings_check := and(openings_check, lt(calldataload(p), r_mod))
+
+ // proof_r_at_zeta
+ p := add(aproof, proof_r_at_zeta)
+ openings_check := and(openings_check, lt(calldataload(p), r_mod))
+
+ // proof_o_at_zeta
+ p := add(aproof, proof_o_at_zeta)
+ openings_check := and(openings_check, lt(calldataload(p), r_mod))
+
+ // proof_s1_at_zeta
+ p := add(aproof, proof_s1_at_zeta)
+ openings_check := and(openings_check, lt(calldataload(p), r_mod))
+
+ // proof_s2_at_zeta
+ p := add(aproof, proof_s2_at_zeta)
+ openings_check := and(openings_check, lt(calldataload(p), r_mod))
+
+ // proof_grand_product_at_zeta_omega
+ p := add(aproof, proof_grand_product_at_zeta_omega)
+ openings_check := and(openings_check, lt(calldataload(p), r_mod))
+
+ // proof_openings_selector_commit_api_at_zeta
+
+ p := add(aproof, proof_openings_selector_commit_api_at_zeta)
+ for {
+ let i := 0
+ } lt(i, vk_nb_commitments_commit_api) {
+ i := add(i, 1)
+ } {
+ openings_check := and(openings_check, lt(calldataload(p), r_mod))
+ p := add(p, 0x20)
+ }
+
+ if iszero(openings_check) {
+ error_proof_openings_size()
+ }
+ }
+ // end checks -------------------------------------------------
+
+ // Beginning challenges -------------------------------------------------
+
+ // Derive gamma as Sha256()
+ // where transcript is the concatenation (in this order) of:
+ // * the word "gamma" in ascii, equal to [0x67,0x61,0x6d, 0x6d, 0x61] and encoded as a uint256.
+ // * the commitments to the permutation polynomials S1, S2, S3, where we concatenate the coordinates of those points
+ // * the commitments of Ql, Qr, Qm, Qo, Qk
+ // * the public inputs
+ // * the commitments of the wires related to the custom gates (commitments_wires_commit_api)
+ // * commitments to L, R, O (proof__com_)
+ // The data described above is written starting at mPtr. "gamma" lies on 5 bytes,
+ // and is encoded as a uint256 number n. In basis b = 256, the number looks like this
+ // [0 0 0 .. 0x67 0x61 0x6d, 0x6d, 0x61]. The first non zero entry is at position 27=0x1b
+ // nb_pi, pi respectively number of public inputs and public inputs
+ function derive_gamma(aproof, nb_pi, pi) -> gamma_not_reduced {
+ let state := mload(0x40)
+ let mPtr := add(state, state_last_mem)
+
+ // gamma
+ // gamma in ascii is [0x67,0x61,0x6d, 0x6d, 0x61]
+ // (same for alpha, beta, zeta)
+ mstore(mPtr, 0x67616d6d61) // "gamma"
+
+ mstore(add(mPtr, 0x20), vk_s1_com_x)
+ mstore(add(mPtr, 0x40), vk_s1_com_y)
+ mstore(add(mPtr, 0x60), vk_s2_com_x)
+ mstore(add(mPtr, 0x80), vk_s2_com_y)
+ mstore(add(mPtr, 0xa0), vk_s3_com_x)
+ mstore(add(mPtr, 0xc0), vk_s3_com_y)
+ mstore(add(mPtr, 0xe0), vk_ql_com_x)
+ mstore(add(mPtr, 0x100), vk_ql_com_y)
+ mstore(add(mPtr, 0x120), vk_qr_com_x)
+ mstore(add(mPtr, 0x140), vk_qr_com_y)
+ mstore(add(mPtr, 0x160), vk_qm_com_x)
+ mstore(add(mPtr, 0x180), vk_qm_com_y)
+ mstore(add(mPtr, 0x1a0), vk_qo_com_x)
+ mstore(add(mPtr, 0x1c0), vk_qo_com_y)
+ mstore(add(mPtr, 0x1e0), vk_qk_com_x)
+ mstore(add(mPtr, 0x200), vk_qk_com_y)
+
+ // public inputs
+ let _mPtr := add(mPtr, 0x220)
+ let size_pi_in_bytes := mul(nb_pi, 0x20)
+ calldatacopy(_mPtr, pi, size_pi_in_bytes)
+ _mPtr := add(_mPtr, size_pi_in_bytes)
+
+ // wire commitment commit api
+ let _proof := add(aproof, proof_openings_selector_commit_api_at_zeta)
+ _proof := add(_proof, mul(vk_nb_commitments_commit_api, 0x20))
+ let size_wire_commitments_commit_api_in_bytes := mul(vk_nb_commitments_commit_api, 0x40)
+ calldatacopy(_mPtr, _proof, size_wire_commitments_commit_api_in_bytes)
+ _mPtr := add(_mPtr, size_wire_commitments_commit_api_in_bytes)
+
+ // commitments to l, r, o
+ let size_commitments_lro_in_bytes := 0xc0
+ calldatacopy(_mPtr, aproof, size_commitments_lro_in_bytes)
+ _mPtr := add(_mPtr, size_commitments_lro_in_bytes)
+
+ let size := add(0x2c5, mul(nb_pi, 0x20)) // 0x2c5 = 22*32+5
+ size := add(size, mul(vk_nb_commitments_commit_api, 0x40))
+ let l_success := staticcall(gas(), 0x2, add(mPtr, 0x1b), size, mPtr, 0x20) //0x1b -> 000.."gamma"
+ if iszero(l_success) {
+ error_verify()
+ }
+ gamma_not_reduced := mload(mPtr)
+ mstore(add(state, state_gamma), mod(gamma_not_reduced, r_mod))
+ }
+
+ function derive_beta(gamma_not_reduced) -> beta_not_reduced {
+ let state := mload(0x40)
+ let mPtr := add(mload(0x40), state_last_mem)
+
+ // beta
+ mstore(mPtr, 0x62657461) // "beta"
+ mstore(add(mPtr, 0x20), gamma_not_reduced)
+ let l_success := staticcall(gas(), 0x2, add(mPtr, 0x1c), 0x24, mPtr, 0x20) //0x1b -> 000.."gamma"
+ if iszero(l_success) {
+ error_verify()
+ }
+ beta_not_reduced := mload(mPtr)
+ mstore(add(state, state_beta), mod(beta_not_reduced, r_mod))
+ }
+
+ // alpha depends on the previous challenge (beta) and on the commitment to the grand product polynomial
+ function derive_alpha(aproof, beta_not_reduced) -> alpha_not_reduced {
+ let state := mload(0x40)
+ let mPtr := add(mload(0x40), state_last_mem)
+
+ // alpha
+ mstore(mPtr, 0x616C706861) // "alpha"
+ mstore(add(mPtr, 0x20), beta_not_reduced)
+ calldatacopy(add(mPtr, 0x40), add(aproof, proof_grand_product_commitment_x), 0x40)
+ let l_success := staticcall(gas(), 0x2, add(mPtr, 0x1b), 0x65, mPtr, 0x20) //0x1b -> 000.."gamma"
+ if iszero(l_success) {
+ error_verify()
+ }
+ alpha_not_reduced := mload(mPtr)
+ mstore(add(state, state_alpha), mod(alpha_not_reduced, r_mod))
+ }
+
+ // zeta depends on the previous challenge (alpha) and on the commitment to the quotient polynomial
+ function derive_zeta(aproof, alpha_not_reduced) {
+ let state := mload(0x40)
+ let mPtr := add(mload(0x40), state_last_mem)
+
+ // zeta
+ mstore(mPtr, 0x7a657461) // "zeta"
+ mstore(add(mPtr, 0x20), alpha_not_reduced)
+ calldatacopy(add(mPtr, 0x40), add(aproof, proof_h_0_x), 0xc0)
+ let l_success := staticcall(gas(), 0x2, add(mPtr, 0x1c), 0xe4, mPtr, 0x20)
+ if iszero(l_success) {
+ error_verify()
+ }
+ let zeta_not_reduced := mload(mPtr)
+ mstore(add(state, state_zeta), mod(zeta_not_reduced, r_mod))
+ }
+ // END challenges -------------------------------------------------
+
+ // BEGINNING compute_pi -------------------------------------------------
+
+ // public input (not comming from the commit api) contribution
+ // ins, n are the public inputs and number of public inputs respectively
+ function sum_pi_wo_api_commit(ins, n, mPtr) -> pi_wo_commit {
+ let state := mload(0x40)
+ let z := mload(add(state, state_zeta))
+ let zpnmo := mload(add(state, state_zeta_power_n_minus_one))
+
+ let li := mPtr
+ batch_compute_lagranges_at_z(z, zpnmo, n, li)
+
+ let tmp := 0
+ for {
+ let i := 0
+ } lt(i, n) {
+ i := add(i, 1)
+ } {
+ tmp := mulmod(mload(li), calldataload(ins), r_mod)
+ pi_wo_commit := addmod(pi_wo_commit, tmp, r_mod)
+ li := add(li, 0x20)
+ ins := add(ins, 0x20)
+ }
+ }
+
+ // mPtr <- [L_0(z), .., L_{n-1}(z)]
+ //
+ // Here L_i(zeta) = Īâą/n * (Îļâŋ-1)/(Îļ-Īâą) where:
+ // * n = vk_domain_size
+ // * Ī = vk_omega (generator of the multiplicative cyclic group of order n in (â¤/râ¤)*)
+ // * Îļ = z (challenge derived with Fiat Shamir)
+ // * zpnmo = 'zeta power n minus one' (Îļâŋ-1) which has been precomputed
+ function batch_compute_lagranges_at_z(z, zpnmo, n, mPtr) {
+ let zn := mulmod(zpnmo, vk_inv_domain_size, r_mod) // 1/n * (Îļâŋ - 1)
+
+ let _w := 1
+ let _mPtr := mPtr
+ for {
+ let i := 0
+ } lt(i, n) {
+ i := add(i, 1)
+ } {
+ mstore(_mPtr, addmod(z, sub(r_mod, _w), r_mod))
+ _w := mulmod(_w, vk_omega, r_mod)
+ _mPtr := add(_mPtr, 0x20)
+ }
+ batch_invert(mPtr, n, _mPtr)
+ _mPtr := mPtr
+ _w := 1
+ for {
+ let i := 0
+ } lt(i, n) {
+ i := add(i, 1)
+ } {
+ mstore(_mPtr, mulmod(mulmod(mload(_mPtr), zn, r_mod), _w, r_mod))
+ _mPtr := add(_mPtr, 0x20)
+ _w := mulmod(_w, vk_omega, r_mod)
+ }
+ }
+
+ // batch invert (modulo r) in place the nb_ins uint256 inputs starting at ins.
+ function batch_invert(ins, nb_ins, mPtr) {
+ mstore(mPtr, 1)
+ let offset := 0
+ for {
+ let i := 0
+ } lt(i, nb_ins) {
+ i := add(i, 1)
+ } {
+ let prev := mload(add(mPtr, offset))
+ let cur := mload(add(ins, offset))
+ cur := mulmod(prev, cur, r_mod)
+ offset := add(offset, 0x20)
+ mstore(add(mPtr, offset), cur)
+ }
+ ins := add(ins, sub(offset, 0x20))
+ mPtr := add(mPtr, offset)
+ let inv := pow(mload(mPtr), sub(r_mod, 2), add(mPtr, 0x20))
+ for {
+ let i := 0
+ } lt(i, nb_ins) {
+ i := add(i, 1)
+ } {
+ mPtr := sub(mPtr, 0x20)
+ let tmp := mload(ins)
+ let cur := mulmod(inv, mload(mPtr), r_mod)
+ mstore(ins, cur)
+ inv := mulmod(inv, tmp, r_mod)
+ ins := sub(ins, 0x20)
+ }
+ }
+
+ // mPtr free memory. Computes the public input contribution related to the commit
+ function sum_pi_commit(aproof, nb_public_inputs, mPtr) -> pi_commit {
+ let state := mload(0x40)
+ let z := mload(add(state, state_zeta))
+ let zpnmo := mload(add(state, state_zeta_power_n_minus_one))
+
+ let p := add(aproof, proof_openings_selector_commit_api_at_zeta)
+ p := add(p, mul(vk_nb_commitments_commit_api, 0x20)) // p points now to the wire commitments
+
+ let h_fr, ith_lagrange
+
+ h_fr := hash_fr(calldataload(p), calldataload(add(p, 0x20)), mPtr)
+ ith_lagrange := compute_ith_lagrange_at_z(z, zpnmo, add(nb_public_inputs, vk_index_commit_api_0), mPtr)
+ pi_commit := addmod(pi_commit, mulmod(h_fr, ith_lagrange, r_mod), r_mod)
+ p := add(p, 0x40)
+ }
+
+ // z zeta
+ // zpmno Îļâŋ-1
+ // i i-th lagrange
+ // mPtr free memory
+ // Computes L_i(zeta) = Īâą/n * (Îļâŋ-1)/(Îļ-Īâą) where:
+ function compute_ith_lagrange_at_z(z, zpnmo, i, mPtr) -> res {
+ let w := pow(vk_omega, i, mPtr) // w**i
+ i := addmod(z, sub(r_mod, w), r_mod) // z-w**i
+ w := mulmod(w, vk_inv_domain_size, r_mod) // w**i/n
+ i := pow(i, sub(r_mod, 2), mPtr) // (z-w**i)**-1
+ w := mulmod(w, i, r_mod) // w**i/n*(z-w)**-1
+ res := mulmod(w, zpnmo, r_mod)
+ }
+
+ // (x, y) point on bn254, both on 32bytes
+ // mPtr free memory
+ function hash_fr(x, y, mPtr) -> res {
+ // [0x00, .. , 0x00 || x, y, || 0, 48, 0, dst, sizeDomain]
+ // <- 64 bytes -> <-64b -> <- 1 bytes each ->
+
+ // [0x00, .., 0x00] 64 bytes of zero
+ mstore(mPtr, zero_uint256)
+ mstore(add(mPtr, 0x20), zero_uint256)
+
+ // msg = x || y , both on 32 bytes
+ mstore(add(mPtr, 0x40), x)
+ mstore(add(mPtr, 0x60), y)
+
+ // 0 || 48 || 0 all on 1 byte
+ mstore8(add(mPtr, 0x80), 0)
+ mstore8(add(mPtr, 0x81), lenInBytes)
+ mstore8(add(mPtr, 0x82), 0)
+
+ // "BSB22-Plonk" = [42, 53, 42, 32, 32, 2d, 50, 6c, 6f, 6e, 6b,]
+ mstore8(add(mPtr, 0x83), 0x42)
+ mstore8(add(mPtr, 0x84), 0x53)
+ mstore8(add(mPtr, 0x85), 0x42)
+ mstore8(add(mPtr, 0x86), 0x32)
+ mstore8(add(mPtr, 0x87), 0x32)
+ mstore8(add(mPtr, 0x88), 0x2d)
+ mstore8(add(mPtr, 0x89), 0x50)
+ mstore8(add(mPtr, 0x8a), 0x6c)
+ mstore8(add(mPtr, 0x8b), 0x6f)
+ mstore8(add(mPtr, 0x8c), 0x6e)
+ mstore8(add(mPtr, 0x8d), 0x6b)
+
+ // size domain
+ mstore8(add(mPtr, 0x8e), sizeDomain)
+
+ let l_success := staticcall(gas(), 0x2, mPtr, 0x8f, mPtr, 0x20)
+ if iszero(l_success) {
+ error_verify()
+ }
+
+ let b0 := mload(mPtr)
+
+ // [b0 || one || dst || sizeDomain]
+ // <-64bytes -> <- 1 byte each ->
+ mstore8(add(mPtr, 0x20), one) // 1
+
+ mstore8(add(mPtr, 0x21), 0x42) // dst
+ mstore8(add(mPtr, 0x22), 0x53)
+ mstore8(add(mPtr, 0x23), 0x42)
+ mstore8(add(mPtr, 0x24), 0x32)
+ mstore8(add(mPtr, 0x25), 0x32)
+ mstore8(add(mPtr, 0x26), 0x2d)
+ mstore8(add(mPtr, 0x27), 0x50)
+ mstore8(add(mPtr, 0x28), 0x6c)
+ mstore8(add(mPtr, 0x29), 0x6f)
+ mstore8(add(mPtr, 0x2a), 0x6e)
+ mstore8(add(mPtr, 0x2b), 0x6b)
+
+ mstore8(add(mPtr, 0x2c), sizeDomain) // size domain
+ l_success := staticcall(gas(), 0x2, mPtr, 0x2d, mPtr, 0x20)
+ if iszero(l_success) {
+ error_verify()
+ }
+
+ // b1 is located at mPtr. We store b2 at add(mPtr, 0x20)
+
+ // [b0^b1 || two || dst || sizeDomain]
+ // <-64bytes -> <- 1 byte each ->
+ mstore(add(mPtr, 0x20), xor(mload(mPtr), b0))
+ mstore8(add(mPtr, 0x40), two)
+
+ mstore8(add(mPtr, 0x41), 0x42) // dst
+ mstore8(add(mPtr, 0x42), 0x53)
+ mstore8(add(mPtr, 0x43), 0x42)
+ mstore8(add(mPtr, 0x44), 0x32)
+ mstore8(add(mPtr, 0x45), 0x32)
+ mstore8(add(mPtr, 0x46), 0x2d)
+ mstore8(add(mPtr, 0x47), 0x50)
+ mstore8(add(mPtr, 0x48), 0x6c)
+ mstore8(add(mPtr, 0x49), 0x6f)
+ mstore8(add(mPtr, 0x4a), 0x6e)
+ mstore8(add(mPtr, 0x4b), 0x6b)
+
+ mstore8(add(mPtr, 0x4c), sizeDomain) // size domain
+
+ let offset := add(mPtr, 0x20)
+ l_success := staticcall(gas(), 0x2, offset, 0x2d, offset, 0x20)
+ if iszero(l_success) {
+ error_verify()
+ }
+
+ // at this point we have mPtr = [ b1 || b2] where b1 is on 32byes and b2 in 16bytes.
+ // we interpret it as a big integer mod r in big endian (similar to regular decimal notation)
+ // the result is then 2**(8*16)*mPtr[32:] + mPtr[32:48]
+ res := mulmod(mload(mPtr), bb, r_mod) // <- res = 2**128 * mPtr[:32]
+ offset := add(mPtr, 0x10)
+ for {
+ let i := 0
+ } lt(i, 0x10) {
+ i := add(i, 1)
+ } {
+ // mPtr <- [xx, xx, .., | 0, 0, .. 0 || b2 ]
+ mstore8(offset, 0x00)
+ offset := add(offset, 0x1)
+ }
+ let b1 := mload(add(mPtr, 0x10)) // b1 <- [0, 0, .., 0 || b2[:16] ]
+ res := addmod(res, b1, r_mod)
+ }
+
+ // END compute_pi -------------------------------------------------
+
+ // compute ι² * 1/n * (Îļ{n}-1)/(Îļ - 1) where
+ // * Îą = challenge derived in derive_gamma_beta_alpha_zeta
+ // * n = vk_domain_size
+ // * Ī = vk_omega (generator of the multiplicative cyclic group of order n in (â¤/râ¤)*)
+ // * Îļ = zeta (challenge derived with Fiat Shamir)
+ function compute_alpha_square_lagrange_0() {
+ let state := mload(0x40)
+ let mPtr := add(mload(0x40), state_last_mem)
+
+ let res := mload(add(state, state_zeta_power_n_minus_one))
+ let den := addmod(mload(add(state, state_zeta)), sub(r_mod, 1), r_mod)
+ den := pow(den, sub(r_mod, 2), mPtr)
+ den := mulmod(den, vk_inv_domain_size, r_mod)
+ res := mulmod(den, res, r_mod)
+
+ let l_alpha := mload(add(state, state_alpha))
+ res := mulmod(res, l_alpha, r_mod)
+ res := mulmod(res, l_alpha, r_mod)
+ mstore(add(state, state_alpha_square_lagrange_0), res)
+ }
+
+ // follows alg. p.13 of https://eprint.iacr.org/2019/953.pdf
+ // with tâ = tâ = 1, and the proofs are ([digest] + [quotient] +purported evaluation):
+ // * [state_folded_state_digests], [proof_batch_opening_at_zeta_x], state_folded_evals
+ // * [proof_grand_product_commitment], [proof_opening_at_zeta_omega_x], [proof_grand_product_at_zeta_omega]
+ function batch_verify_multi_points(aproof) {
+ let state := mload(0x40)
+ let mPtr := add(state, state_last_mem)
+
+ // here the random is not a challenge, hence no need to use Fiat Shamir, we just
+ // need an unpredictible result.
+ let random := mod(keccak256(state, 0x20), r_mod)
+
+ let folded_quotients := mPtr
+ mPtr := add(folded_quotients, 0x40)
+ mstore(folded_quotients, calldataload(add(aproof, proof_batch_opening_at_zeta_x)))
+ mstore(add(folded_quotients, 0x20), calldataload(add(aproof, proof_batch_opening_at_zeta_y)))
+ point_acc_mul_calldata(folded_quotients, add(aproof, proof_opening_at_zeta_omega_x), random, mPtr)
+
+ let folded_digests := add(state, state_folded_digests_x)
+ point_acc_mul_calldata(folded_digests, add(aproof, proof_grand_product_commitment_x), random, mPtr)
+
+ let folded_evals := add(state, state_folded_claimed_values)
+ fr_acc_mul_calldata(folded_evals, add(aproof, proof_grand_product_at_zeta_omega), random)
+
+ let folded_evals_commit := mPtr
+ mPtr := add(folded_evals_commit, 0x40)
+ mstore(folded_evals_commit, 14312776538779914388377568895031746459131577658076416373430523308756343304251)
+ mstore(
+ add(folded_evals_commit, 0x20),
+ 11763105256161367503191792604679297387056316997144156930871823008787082098465
+ )
+ mstore(add(folded_evals_commit, 0x40), mload(folded_evals))
+ let check_staticcall := staticcall(gas(), 7, folded_evals_commit, 0x60, folded_evals_commit, 0x40)
+ if eq(check_staticcall, 0) {
+ error_verify()
+ }
+
+ let folded_evals_commit_y := add(folded_evals_commit, 0x20)
+ mstore(folded_evals_commit_y, sub(p_mod, mload(folded_evals_commit_y)))
+ point_add(folded_digests, folded_digests, folded_evals_commit, mPtr)
+
+ let folded_points_quotients := mPtr
+ mPtr := add(mPtr, 0x40)
+ point_mul_calldata(
+ folded_points_quotients,
+ add(aproof, proof_batch_opening_at_zeta_x),
+ mload(add(state, state_zeta)),
+ mPtr
+ )
+ let zeta_omega := mulmod(mload(add(state, state_zeta)), vk_omega, r_mod)
+ random := mulmod(random, zeta_omega, r_mod)
+ point_acc_mul_calldata(folded_points_quotients, add(aproof, proof_opening_at_zeta_omega_x), random, mPtr)
+
+ point_add(folded_digests, folded_digests, folded_points_quotients, mPtr)
+
+ let folded_quotients_y := add(folded_quotients, 0x20)
+ mstore(folded_quotients_y, sub(p_mod, mload(folded_quotients_y)))
+
+ mstore(mPtr, mload(folded_digests))
+ mstore(add(mPtr, 0x20), mload(add(folded_digests, 0x20)))
+ mstore(add(mPtr, 0x40), g2_srs_0_x_0) // the 4 lines are the canonical G2 point on BN254
+ mstore(add(mPtr, 0x60), g2_srs_0_x_1)
+ mstore(add(mPtr, 0x80), g2_srs_0_y_0)
+ mstore(add(mPtr, 0xa0), g2_srs_0_y_1)
+ mstore(add(mPtr, 0xc0), mload(folded_quotients))
+ mstore(add(mPtr, 0xe0), mload(add(folded_quotients, 0x20)))
+ mstore(add(mPtr, 0x100), g2_srs_1_x_0)
+ mstore(add(mPtr, 0x120), g2_srs_1_x_1)
+ mstore(add(mPtr, 0x140), g2_srs_1_y_0)
+ mstore(add(mPtr, 0x160), g2_srs_1_y_1)
+ check_pairing_kzg(mPtr)
+ }
+
+ // check_pairing_kzg checks the result of the final pairing product of the batched
+ // kzg verification. The purpose of this function is too avoid exhausting the stack
+ // in the function batch_verify_multi_points.
+ // mPtr: pointer storing the tuple of pairs
+ function check_pairing_kzg(mPtr) {
+ let state := mload(0x40)
+
+ // TODO test the staticcall using the method from audit_4-5
+ let l_success := staticcall(gas(), 8, mPtr, 0x180, 0x00, 0x20)
+ let res_pairing := mload(0x00)
+ let s_success := mload(add(state, state_success))
+ res_pairing := and(and(res_pairing, l_success), s_success)
+ mstore(add(state, state_success), res_pairing)
+ }
+
+ // Fold the opening proofs at Îļ:
+ // * at state+state_folded_digest we store: [H] + Îŗ[Linearised_polynomial]+Îŗ²[L] + ÎŗÂŗ[R] + Îŗâ´[O] + Îŗâĩ[Sâ] +Îŗâļ[Sâ] + âáĩĸÎŗâļâēâą[Pi_{i}]
+ // * at state+state_folded_claimed_values we store: H(Îļ) + ÎŗLinearised_polynomial(Îļ)+Îŗ²L(Îļ) + ÎŗÂŗR(Îļ)+ Îŗâ´O(Îļ) + ÎŗâĩSâ(Îļ) +ÎŗâļSâ(Îļ) + âáĩĸÎŗâļâēâąPi_{i}(Îļ)
+ // acc_gamma stores the Îŗâą
+ function fold_state(aproof) {
+ let state := mload(0x40)
+ let mPtr := add(mload(0x40), state_last_mem)
+
+ let l_gamma_kzg := mload(add(state, state_gamma_kzg))
+ let acc_gamma := l_gamma_kzg
+
+ let offset := add(0x200, mul(vk_nb_commitments_commit_api, 0x40)) // 0x40 = 2*0x20
+ let mPtrOffset := add(mPtr, offset)
+
+ mstore(add(state, state_folded_digests_x), mload(add(mPtr, 0x40)))
+ mstore(add(state, state_folded_digests_y), mload(add(mPtr, 0x60)))
+ mstore(add(state, state_folded_claimed_values), calldataload(add(aproof, proof_quotient_polynomial_at_zeta)))
+
+ point_acc_mul(add(state, state_folded_digests_x), add(mPtr, 0x80), acc_gamma, mPtrOffset)
+ fr_acc_mul_calldata(
+ add(state, state_folded_claimed_values),
+ add(aproof, proof_linearised_polynomial_at_zeta),
+ acc_gamma
+ )
+
+ acc_gamma := mulmod(acc_gamma, l_gamma_kzg, r_mod)
+ point_acc_mul(add(state, state_folded_digests_x), add(mPtr, 0xc0), acc_gamma, mPtrOffset)
+ fr_acc_mul_calldata(add(state, state_folded_claimed_values), add(aproof, proof_l_at_zeta), acc_gamma)
+
+ acc_gamma := mulmod(acc_gamma, l_gamma_kzg, r_mod)
+ point_acc_mul(add(state, state_folded_digests_x), add(mPtr, 0x100), acc_gamma, add(mPtr, offset))
+ fr_acc_mul_calldata(add(state, state_folded_claimed_values), add(aproof, proof_r_at_zeta), acc_gamma)
+
+ acc_gamma := mulmod(acc_gamma, l_gamma_kzg, r_mod)
+ point_acc_mul(add(state, state_folded_digests_x), add(mPtr, 0x140), acc_gamma, add(mPtr, offset))
+ fr_acc_mul_calldata(add(state, state_folded_claimed_values), add(aproof, proof_o_at_zeta), acc_gamma)
+
+ acc_gamma := mulmod(acc_gamma, l_gamma_kzg, r_mod)
+ point_acc_mul(add(state, state_folded_digests_x), add(mPtr, 0x180), acc_gamma, add(mPtr, offset))
+ fr_acc_mul_calldata(add(state, state_folded_claimed_values), add(aproof, proof_s1_at_zeta), acc_gamma)
+
+ acc_gamma := mulmod(acc_gamma, l_gamma_kzg, r_mod)
+ point_acc_mul(add(state, state_folded_digests_x), add(mPtr, 0x1c0), acc_gamma, add(mPtr, offset))
+ fr_acc_mul_calldata(add(state, state_folded_claimed_values), add(aproof, proof_s2_at_zeta), acc_gamma)
+
+ let poscaz := add(aproof, proof_openings_selector_commit_api_at_zeta)
+ let opca := add(mPtr, 0x200) // offset_proof_commits_api
+ for {
+ let i := 0
+ } lt(i, vk_nb_commitments_commit_api) {
+ i := add(i, 1)
+ } {
+ acc_gamma := mulmod(acc_gamma, l_gamma_kzg, r_mod)
+ point_acc_mul(add(state, state_folded_digests_x), opca, acc_gamma, add(mPtr, offset))
+ fr_acc_mul_calldata(add(state, state_folded_claimed_values), poscaz, acc_gamma)
+ poscaz := add(poscaz, 0x20)
+ opca := add(opca, 0x40)
+ }
+ }
+
+ // generate the challenge (using Fiat Shamir) to fold the opening proofs
+ // at Îļ.
+ // The process for deriving Îŗ is the same as in derive_gamma but this time the inputs are
+ // in this order (the [] means it's a commitment):
+ // * Îļ
+ // * [H] ( = Hâ + Îļáĩâē²*Hâ + Îļ²âŊáĩâē²âž*Hâ )
+ // * [Linearised polynomial]
+ // * [L], [R], [O]
+ // * [Sâ] [Sâ]
+ // * [Pi_{i}] (wires associated to custom gates)
+ // Then there are the purported evaluations of the previous committed polynomials:
+ // * H(Îļ)
+ // * Linearised_polynomial(Îļ)
+ // * L(Îļ), R(Îļ), O(Îļ), Sâ(Îļ), Sâ(Îļ)
+ // * Pi_{i}(Îļ)
+ function compute_gamma_kzg(aproof) {
+ let state := mload(0x40)
+ let mPtr := add(mload(0x40), state_last_mem)
+ mstore(mPtr, 0x67616d6d61) // "gamma"
+ mstore(add(mPtr, 0x20), mload(add(state, state_zeta)))
+ mstore(add(mPtr, 0x40), mload(add(state, state_folded_h_x)))
+ mstore(add(mPtr, 0x60), mload(add(state, state_folded_h_y)))
+ mstore(add(mPtr, 0x80), mload(add(state, state_linearised_polynomial_x)))
+ mstore(add(mPtr, 0xa0), mload(add(state, state_linearised_polynomial_y)))
+ calldatacopy(add(mPtr, 0xc0), add(aproof, proof_l_com_x), 0xc0)
+ mstore(add(mPtr, 0x180), vk_s1_com_x)
+ mstore(add(mPtr, 0x1a0), vk_s1_com_y)
+ mstore(add(mPtr, 0x1c0), vk_s2_com_x)
+ mstore(add(mPtr, 0x1e0), vk_s2_com_y)
+
+ let offset := 0x200
+
+ mstore(add(mPtr, offset), vk_selector_commitments_commit_api_0_x)
+ mstore(add(mPtr, add(offset, 0x20)), vk_selector_commitments_commit_api_0_y)
+ offset := add(offset, 0x40)
+
+ mstore(add(mPtr, offset), calldataload(add(aproof, proof_quotient_polynomial_at_zeta)))
+ mstore(add(mPtr, add(offset, 0x20)), calldataload(add(aproof, proof_linearised_polynomial_at_zeta)))
+ mstore(add(mPtr, add(offset, 0x40)), calldataload(add(aproof, proof_l_at_zeta)))
+ mstore(add(mPtr, add(offset, 0x60)), calldataload(add(aproof, proof_r_at_zeta)))
+ mstore(add(mPtr, add(offset, 0x80)), calldataload(add(aproof, proof_o_at_zeta)))
+ mstore(add(mPtr, add(offset, 0xa0)), calldataload(add(aproof, proof_s1_at_zeta)))
+ mstore(add(mPtr, add(offset, 0xc0)), calldataload(add(aproof, proof_s2_at_zeta)))
+
+ let _mPtr := add(mPtr, add(offset, 0xe0))
+ let _poscaz := add(aproof, proof_openings_selector_commit_api_at_zeta)
+ for {
+ let i := 0
+ } lt(i, vk_nb_commitments_commit_api) {
+ i := add(i, 1)
+ } {
+ mstore(_mPtr, calldataload(_poscaz))
+ _poscaz := add(_poscaz, 0x20)
+ _mPtr := add(_mPtr, 0x20)
+ }
+
+ let start_input := 0x1b // 00.."gamma"
+ let size_input := add(0x16, mul(vk_nb_commitments_commit_api, 3)) // number of 32bytes elmts = 0x16 (zeta+2*7+7 for the digests+openings) + 2*vk_nb_commitments_commit_api (for the commitments of the selectors) + vk_nb_commitments_commit_api (for the openings of the selectors)
+ size_input := add(0x5, mul(size_input, 0x20)) // size in bytes: 15*32 bytes + 5 bytes for gamma
+ let check_staticcall := staticcall(
+ gas(),
+ 0x2,
+ add(mPtr, start_input),
+ size_input,
+ add(state, state_gamma_kzg),
+ 0x20
+ )
+ if eq(check_staticcall, 0) {
+ error_verify()
+ }
+ mstore(add(state, state_gamma_kzg), mod(mload(add(state, state_gamma_kzg)), r_mod))
+ }
+
+ function compute_commitment_linearised_polynomial_ec(aproof, s1, s2) {
+ let state := mload(0x40)
+ let mPtr := add(mload(0x40), state_last_mem)
+
+ mstore(mPtr, vk_ql_com_x)
+ mstore(add(mPtr, 0x20), vk_ql_com_y)
+ point_mul(
+ add(state, state_linearised_polynomial_x),
+ mPtr,
+ calldataload(add(aproof, proof_l_at_zeta)),
+ add(mPtr, 0x40)
+ )
+
+ mstore(mPtr, vk_qr_com_x)
+ mstore(add(mPtr, 0x20), vk_qr_com_y)
+ point_acc_mul(
+ add(state, state_linearised_polynomial_x),
+ mPtr,
+ calldataload(add(aproof, proof_r_at_zeta)),
+ add(mPtr, 0x40)
+ )
+
+ let rl := mulmod(calldataload(add(aproof, proof_l_at_zeta)), calldataload(add(aproof, proof_r_at_zeta)), r_mod)
+ mstore(mPtr, vk_qm_com_x)
+ mstore(add(mPtr, 0x20), vk_qm_com_y)
+ point_acc_mul(add(state, state_linearised_polynomial_x), mPtr, rl, add(mPtr, 0x40))
+
+ mstore(mPtr, vk_qo_com_x)
+ mstore(add(mPtr, 0x20), vk_qo_com_y)
+ point_acc_mul(
+ add(state, state_linearised_polynomial_x),
+ mPtr,
+ calldataload(add(aproof, proof_o_at_zeta)),
+ add(mPtr, 0x40)
+ )
+
+ mstore(mPtr, vk_qk_com_x)
+ mstore(add(mPtr, 0x20), vk_qk_com_y)
+ point_add(
+ add(state, state_linearised_polynomial_x),
+ add(state, state_linearised_polynomial_x),
+ mPtr,
+ add(mPtr, 0x40)
+ )
+
+ let commits_api_at_zeta := add(aproof, proof_openings_selector_commit_api_at_zeta)
+ let commits_api := add(
+ aproof,
+ add(proof_openings_selector_commit_api_at_zeta, mul(vk_nb_commitments_commit_api, 0x20))
+ )
+ for {
+ let i := 0
+ } lt(i, vk_nb_commitments_commit_api) {
+ i := add(i, 1)
+ } {
+ mstore(mPtr, calldataload(commits_api))
+ mstore(add(mPtr, 0x20), calldataload(add(commits_api, 0x20)))
+ point_acc_mul(
+ add(state, state_linearised_polynomial_x),
+ mPtr,
+ calldataload(commits_api_at_zeta),
+ add(mPtr, 0x40)
+ )
+ commits_api_at_zeta := add(commits_api_at_zeta, 0x20)
+ commits_api := add(commits_api, 0x40)
+ }
+
+ mstore(mPtr, vk_s3_com_x)
+ mstore(add(mPtr, 0x20), vk_s3_com_y)
+ point_acc_mul(add(state, state_linearised_polynomial_x), mPtr, s1, add(mPtr, 0x40))
+
+ mstore(mPtr, calldataload(add(aproof, proof_grand_product_commitment_x)))
+ mstore(add(mPtr, 0x20), calldataload(add(aproof, proof_grand_product_commitment_y)))
+ point_acc_mul(add(state, state_linearised_polynomial_x), mPtr, s2, add(mPtr, 0x40))
+ }
+
+ // Compute the commitment to the linearized polynomial equal to
+ // L(Îļ)[Qâ]+r(Îļ)[QáĩŖ]+R(Îļ)L(Îļ)[Qâ]+O(Îļ)[Qâ]+[Qâ]+ÎŖáĩĸqc'áĩĸ(Îļ)[BsbCommitmentáĩĸ] +
+ // Îą*( Z(ÎŧÎļ)(L(Îļ)+β*Sâ(Îļ)+Îŗ)*(R(Îļ)+β*Sâ(Îļ)+Îŗ)[Sâ]-[Z](L(Îļ)+β*id_{1}(Îļ)+Îŗ)*(R(Îļ)+β*id_{2(Îļ)+Îŗ)*(O(Îļ)+β*id_{3}(Îļ)+Îŗ) ) +
+ // ι²*Lâ(Îļ)[Z]
+ // where
+ // * id_1 = id, id_2 = vk_coset_shift*id, id_3 = vk_coset_shift^{2}*id
+ // * the [] means that it's a commitment (i.e. a point on Bn254(F_p))
+ function compute_commitment_linearised_polynomial(aproof) {
+ let state := mload(0x40)
+ let l_beta := mload(add(state, state_beta))
+ let l_gamma := mload(add(state, state_gamma))
+ let l_zeta := mload(add(state, state_zeta))
+ let l_alpha := mload(add(state, state_alpha))
+
+ let u := mulmod(calldataload(add(aproof, proof_grand_product_at_zeta_omega)), l_beta, r_mod)
+ let v := mulmod(l_beta, calldataload(add(aproof, proof_s1_at_zeta)), r_mod)
+ v := addmod(v, calldataload(add(aproof, proof_l_at_zeta)), r_mod)
+ v := addmod(v, l_gamma, r_mod)
+
+ let w := mulmod(l_beta, calldataload(add(aproof, proof_s2_at_zeta)), r_mod)
+ w := addmod(w, calldataload(add(aproof, proof_r_at_zeta)), r_mod)
+ w := addmod(w, l_gamma, r_mod)
+
+ let s1 := mulmod(u, v, r_mod)
+ s1 := mulmod(s1, w, r_mod)
+ s1 := mulmod(s1, l_alpha, r_mod)
+
+ let coset_square := mulmod(vk_coset_shift, vk_coset_shift, r_mod)
+ let betazeta := mulmod(l_beta, l_zeta, r_mod)
+ u := addmod(betazeta, calldataload(add(aproof, proof_l_at_zeta)), r_mod)
+ u := addmod(u, l_gamma, r_mod)
+
+ v := mulmod(betazeta, vk_coset_shift, r_mod)
+ v := addmod(v, calldataload(add(aproof, proof_r_at_zeta)), r_mod)
+ v := addmod(v, l_gamma, r_mod)
+
+ w := mulmod(betazeta, coset_square, r_mod)
+ w := addmod(w, calldataload(add(aproof, proof_o_at_zeta)), r_mod)
+ w := addmod(w, l_gamma, r_mod)
+
+ let s2 := mulmod(u, v, r_mod)
+ s2 := mulmod(s2, w, r_mod)
+ s2 := sub(r_mod, s2)
+ s2 := mulmod(s2, l_alpha, r_mod)
+ s2 := addmod(s2, mload(add(state, state_alpha_square_lagrange_0)), r_mod)
+
+ // at this stage:
+ // * sâ = Îą*Z(ÎŧÎļ)(l(Îļ)+β*sâ(Îļ)+Îŗ)*(r(Îļ)+β*sâ(Îļ)+Îŗ)*β
+ // * sâ = -Îą*(l(Îļ)+β*Îļ+Îŗ)*(r(Îļ)+β*u*Îļ+Îŗ)*(o(Îļ)+β*u²*Îļ+Îŗ) + ι²*Lâ(Îļ)
+
+ compute_commitment_linearised_polynomial_ec(aproof, s1, s2)
+ }
+
+ // compute Hâ + Îļáĩâē²*Hâ + Îļ²âŊáĩâē²âž*Hâ and store the result at
+ // state + state_folded_h
+ function fold_h(aproof) {
+ let state := mload(0x40)
+ let n_plus_two := add(vk_domain_size, 2)
+ let mPtr := add(mload(0x40), state_last_mem)
+ let zeta_power_n_plus_two := pow(mload(add(state, state_zeta)), n_plus_two, mPtr)
+ point_mul_calldata(add(state, state_folded_h_x), add(aproof, proof_h_2_x), zeta_power_n_plus_two, mPtr)
+ point_add_calldata(add(state, state_folded_h_x), add(state, state_folded_h_x), add(aproof, proof_h_1_x), mPtr)
+ point_mul(add(state, state_folded_h_x), add(state, state_folded_h_x), zeta_power_n_plus_two, mPtr)
+ point_add_calldata(add(state, state_folded_h_x), add(state, state_folded_h_x), add(aproof, proof_h_0_x), mPtr)
+ }
+
+ // check that
+ // L(Îļ)Qâ(Îļ)+r(Îļ)QáĩŖ(Îļ)+R(Îļ)L(Îļ)Qâ(Îļ)+O(Îļ)Qâ(Îļ)+Qâ(Îļ)+ÎŖáĩĸqc'áĩĸ(Îļ)BsbCommitmentáĩĸ(Îļ) +
+ // Îą*( Z(ÎŧÎļ)(l(Îļ)+β*sâ(Îļ)+Îŗ)*(r(Îļ)+β*sâ(Îļ)+Îŗ)*β*sâ(X)-Z(X)(l(Îļ)+β*id_1(Îļ)+Îŗ)*(r(Îļ)+β*id_2(Îļ)+Îŗ)*(o(Îļ)+β*id_3(Îļ)+Îŗ) ) )
+ // + ι²*Lâ(Îļ) =
+ // (Îļâŋ-1)H(Îļ)
+ function verify_quotient_poly_eval_at_zeta(aproof) {
+ let state := mload(0x40)
+
+ // (l(Îļ)+β*s1(Îļ)+Îŗ)
+ let s1 := add(mload(0x40), state_last_mem)
+ mstore(s1, mulmod(calldataload(add(aproof, proof_s1_at_zeta)), mload(add(state, state_beta)), r_mod))
+ mstore(s1, addmod(mload(s1), mload(add(state, state_gamma)), r_mod))
+ mstore(s1, addmod(mload(s1), calldataload(add(aproof, proof_l_at_zeta)), r_mod))
+
+ // (r(Îļ)+β*s2(Îļ)+Îŗ)
+ let s2 := add(s1, 0x20)
+ mstore(s2, mulmod(calldataload(add(aproof, proof_s2_at_zeta)), mload(add(state, state_beta)), r_mod))
+ mstore(s2, addmod(mload(s2), mload(add(state, state_gamma)), r_mod))
+ mstore(s2, addmod(mload(s2), calldataload(add(aproof, proof_r_at_zeta)), r_mod))
+ // _s2 := mload(s2)
+
+ // (o(Îļ)+Îŗ)
+ let o := add(s1, 0x40)
+ mstore(o, addmod(calldataload(add(aproof, proof_o_at_zeta)), mload(add(state, state_gamma)), r_mod))
+
+ // Îą*(Z(ÎŧÎļ))*(l(Îļ)+β*s1(Îļ)+Îŗ)*(r(Îļ)+β*s2(Îļ)+Îŗ)*(o(Îļ)+Îŗ)
+ mstore(s1, mulmod(mload(s1), mload(s2), r_mod))
+ mstore(s1, mulmod(mload(s1), mload(o), r_mod))
+ mstore(s1, mulmod(mload(s1), mload(add(state, state_alpha)), r_mod))
+ mstore(s1, mulmod(mload(s1), calldataload(add(aproof, proof_grand_product_at_zeta_omega)), r_mod))
+
+ let computed_quotient := add(s1, 0x60)
+
+ // linearizedpolynomial + pi(zeta)
+ mstore(
+ computed_quotient,
+ addmod(calldataload(add(aproof, proof_linearised_polynomial_at_zeta)), mload(add(state, state_pi)), r_mod)
+ )
+ mstore(computed_quotient, addmod(mload(computed_quotient), mload(s1), r_mod))
+ mstore(
+ computed_quotient,
+ addmod(mload(computed_quotient), sub(r_mod, mload(add(state, state_alpha_square_lagrange_0))), r_mod)
+ )
+ mstore(
+ s2,
+ mulmod(
+ calldataload(add(aproof, proof_quotient_polynomial_at_zeta)),
+ mload(add(state, state_zeta_power_n_minus_one)),
+ r_mod
+ )
+ )
+
+ mstore(add(state, state_success), eq(mload(computed_quotient), mload(s2)))
+ }
+
+ // BEGINNING utils math functions -------------------------------------------------
+ function point_add(dst, p, q, mPtr) {
+ let state := mload(0x40)
+ mstore(mPtr, mload(p))
+ mstore(add(mPtr, 0x20), mload(add(p, 0x20)))
+ mstore(add(mPtr, 0x40), mload(q))
+ mstore(add(mPtr, 0x60), mload(add(q, 0x20)))
+ let l_success := staticcall(gas(), 6, mPtr, 0x80, dst, 0x40)
+ if iszero(l_success) {
+ error_ec_op()
+ }
+ }
+
+ function point_add_calldata(dst, p, q, mPtr) {
+ let state := mload(0x40)
+ mstore(mPtr, mload(p))
+ mstore(add(mPtr, 0x20), mload(add(p, 0x20)))
+ mstore(add(mPtr, 0x40), calldataload(q))
+ mstore(add(mPtr, 0x60), calldataload(add(q, 0x20)))
+ let l_success := staticcall(gas(), 6, mPtr, 0x80, dst, 0x40)
+ if iszero(l_success) {
+ error_ec_op()
+ }
+ }
+
+ // dst <- [s]src
+ function point_mul(dst, src, s, mPtr) {
+ let state := mload(0x40)
+ mstore(mPtr, mload(src))
+ mstore(add(mPtr, 0x20), mload(add(src, 0x20)))
+ mstore(add(mPtr, 0x40), s)
+ let l_success := staticcall(gas(), 7, mPtr, 0x60, dst, 0x40)
+ if iszero(l_success) {
+ error_ec_op()
+ }
+ }
+
+ // dst <- [s]src
+ function point_mul_calldata(dst, src, s, mPtr) {
+ let state := mload(0x40)
+ mstore(mPtr, calldataload(src))
+ mstore(add(mPtr, 0x20), calldataload(add(src, 0x20)))
+ mstore(add(mPtr, 0x40), s)
+ let l_success := staticcall(gas(), 7, mPtr, 0x60, dst, 0x40)
+ if iszero(l_success) {
+ error_ec_op()
+ }
+ }
+
+ // dst <- dst + [s]src (Elliptic curve)
+ function point_acc_mul(dst, src, s, mPtr) {
+ let state := mload(0x40)
+ mstore(mPtr, mload(src))
+ mstore(add(mPtr, 0x20), mload(add(src, 0x20)))
+ mstore(add(mPtr, 0x40), s)
+ let l_success := staticcall(gas(), 7, mPtr, 0x60, mPtr, 0x40)
+ mstore(add(mPtr, 0x40), mload(dst))
+ mstore(add(mPtr, 0x60), mload(add(dst, 0x20)))
+ l_success := and(l_success, staticcall(gas(), 6, mPtr, 0x80, dst, 0x40))
+ if iszero(l_success) {
+ error_ec_op()
+ }
+ }
+
+ // dst <- dst + [s]src (Elliptic curve)
+ function point_acc_mul_calldata(dst, src, s, mPtr) {
+ let state := mload(0x40)
+ mstore(mPtr, calldataload(src))
+ mstore(add(mPtr, 0x20), calldataload(add(src, 0x20)))
+ mstore(add(mPtr, 0x40), s)
+ let l_success := staticcall(gas(), 7, mPtr, 0x60, mPtr, 0x40)
+ mstore(add(mPtr, 0x40), mload(dst))
+ mstore(add(mPtr, 0x60), mload(add(dst, 0x20)))
+ l_success := and(l_success, staticcall(gas(), 6, mPtr, 0x80, dst, 0x40))
+ if iszero(l_success) {
+ error_ec_op()
+ }
+ }
+
+ // dst <- dst + src (Fr) dst,src are addresses, s is a value
+ function fr_acc_mul_calldata(dst, src, s) {
+ let tmp := mulmod(calldataload(src), s, r_mod)
+ mstore(dst, addmod(mload(dst), tmp, r_mod))
+ }
+
+ // dst <- x ** e mod r (x, e are values, not pointers)
+ function pow(x, e, mPtr) -> res {
+ mstore(mPtr, 0x20)
+ mstore(add(mPtr, 0x20), 0x20)
+ mstore(add(mPtr, 0x40), 0x20)
+ mstore(add(mPtr, 0x60), x)
+ mstore(add(mPtr, 0x80), e)
+ mstore(add(mPtr, 0xa0), r_mod)
+ let check_staticcall := staticcall(gas(), 0x05, mPtr, 0xc0, mPtr, 0x20)
+ if eq(check_staticcall, 0) {
+ error_verify()
+ }
+ res := mload(mPtr)
+ }
+ }
+ }
+}
diff --git a/contracts/contracts/verifiers/PlonkVerifierFullLarge.sol b/contracts/contracts/verifiers/PlonkVerifierFullLarge.sol
new file mode 100644
index 000000000..8ae8f3d2a
--- /dev/null
+++ b/contracts/contracts/verifiers/PlonkVerifierFullLarge.sol
@@ -0,0 +1,1223 @@
+// SPDX-License-Identifier: Apache-2.0
+
+// Copyright 2023 Consensys Software Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Code generated by gnark DO NOT EDIT
+
+pragma solidity 0.8.19;
+
+contract PlonkVerifierFullLarge {
+ uint256 private constant r_mod = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
+ uint256 private constant p_mod = 21888242871839275222246405745257275088696311157297823662689037894645226208583;
+
+ uint256 private constant g2_srs_0_x_0 = 11559732032986387107991004021392285783925812861821192530917403151452391805634;
+ uint256 private constant g2_srs_0_x_1 = 10857046999023057135944570762232829481370756359578518086990519993285655852781;
+ uint256 private constant g2_srs_0_y_0 = 4082367875863433681332203403145435568316851327593401208105741076214120093531;
+ uint256 private constant g2_srs_0_y_1 = 8495653923123431417604973247489272438418190587263600148770280649306958101930;
+
+ uint256 private constant g2_srs_1_x_0 = 15805639136721018565402881920352193254830339253282065586954346329754995870280;
+ uint256 private constant g2_srs_1_x_1 = 19089565590083334368588890253123139704298730990782503769911324779715431555531;
+ uint256 private constant g2_srs_1_y_0 = 9779648407879205346559610309258181044130619080926897934572699915909528404984;
+ uint256 private constant g2_srs_1_y_1 = 6779728121489434657638426458390319301070371227460768374343986326751507916979;
+
+ // ----------------------- vk ---------------------
+ uint256 private constant vk_domain_size = 33554432;
+ uint256 private constant vk_inv_domain_size =
+ 21888242219518804655518433051623070663413851959604507555939307129453691614729;
+ uint256 private constant vk_omega = 19200870435978225707111062059747084165650991997241425080699860725083300967194;
+ uint256 private constant vk_ql_com_x = 18146005070467268477668032248583408776383044207387355507521882504342342385358;
+ uint256 private constant vk_ql_com_y = 5516740975169060234907789477544407797655156641610876595759657406674306478966;
+ uint256 private constant vk_qr_com_x = 12146364481768237169634134283186266532572188246671190275926369008157198715011;
+ uint256 private constant vk_qr_com_y = 4158539532297281229064265863191552129388916413298961056884571136032050395127;
+ uint256 private constant vk_qm_com_x = 19147207307337995316061234234527046379130640948485432323514624104843352751495;
+ uint256 private constant vk_qm_com_y = 18882331144509724864959157205050817049332697995742760751300304485739776166734;
+ uint256 private constant vk_qo_com_x = 19091455809804893235209183155249970774288746824851329887893221645331348143334;
+ uint256 private constant vk_qo_com_y = 4121621703764897682353925521420321741753269810107824785007668351536476184949;
+ uint256 private constant vk_qk_com_x = 3346865706388696613629052889099460000798752341170167118816816424080998291430;
+ uint256 private constant vk_qk_com_y = 1428964675593940012648567346937818859413896343996149230186628616676041044329;
+
+ uint256 private constant vk_s1_com_x = 16938728398565925424872985934505349190017017881709558376953898999563638544843;
+ uint256 private constant vk_s1_com_y = 3048388969296240622320151132555420126491095316213461523772013343282375225329;
+
+ uint256 private constant vk_s2_com_x = 13947882770829810488272964703246736903777767819512770240361409133045521298117;
+ uint256 private constant vk_s2_com_y = 8731465180913138423283530050792649679145409131816410868106996882400748630955;
+
+ uint256 private constant vk_s3_com_x = 2388947932233667508399049238129714227910354367274846135703769655714826661419;
+ uint256 private constant vk_s3_com_y = 2552117266413842861429350027587256255877998397978448202487083660679276635888;
+
+ uint256 private constant vk_coset_shift = 5;
+
+ uint256 private constant vk_selector_commitments_commit_api_0_x =
+ 3884624064879507288554125065509059886567955998027552754695080199794317463496;
+ uint256 private constant vk_selector_commitments_commit_api_0_y =
+ 3095893745686259387023328325251031582843387681306303361911851672293653329255;
+
+ uint256 private constant vk_index_commit_api_0 = 20133111;
+
+ uint256 private constant vk_nb_commitments_commit_api = 1;
+
+ // ------------------------------------------------
+
+ // offset proof
+ uint256 private constant proof_l_com_x = 0x00;
+ uint256 private constant proof_l_com_y = 0x20;
+ uint256 private constant proof_r_com_x = 0x40;
+ uint256 private constant proof_r_com_y = 0x60;
+ uint256 private constant proof_o_com_x = 0x80;
+ uint256 private constant proof_o_com_y = 0xa0;
+
+ // h = h_0 + x^{n+2}h_1 + x^{2(n+2)}h_2
+ uint256 private constant proof_h_0_x = 0xc0;
+ uint256 private constant proof_h_0_y = 0xe0;
+ uint256 private constant proof_h_1_x = 0x100;
+ uint256 private constant proof_h_1_y = 0x120;
+ uint256 private constant proof_h_2_x = 0x140;
+ uint256 private constant proof_h_2_y = 0x160;
+
+ // wire values at zeta
+ uint256 private constant proof_l_at_zeta = 0x180;
+ uint256 private constant proof_r_at_zeta = 0x1a0;
+ uint256 private constant proof_o_at_zeta = 0x1c0;
+
+ //uint256[STATE_WIDTH-1] permutation_polynomials_at_zeta; // SĪ1(zeta),SĪ2(zeta)
+ uint256 private constant proof_s1_at_zeta = 0x1e0; // SĪ1(zeta)
+ uint256 private constant proof_s2_at_zeta = 0x200; // SĪ2(zeta)
+
+ //Bn254.G1Point grand_product_commitment; // [z(x)]
+ uint256 private constant proof_grand_product_commitment_x = 0x220;
+ uint256 private constant proof_grand_product_commitment_y = 0x240;
+
+ uint256 private constant proof_grand_product_at_zeta_omega = 0x260; // z(w*zeta)
+ uint256 private constant proof_quotient_polynomial_at_zeta = 0x280; // t(zeta)
+ uint256 private constant proof_linearised_polynomial_at_zeta = 0x2a0; // r(zeta)
+
+ // Folded proof for the opening of H, linearised poly, l, r, o, s_1, s_2, qcp
+ uint256 private constant proof_batch_opening_at_zeta_x = 0x2c0; // [Wzeta]
+ uint256 private constant proof_batch_opening_at_zeta_y = 0x2e0;
+
+ //Bn254.G1Point opening_at_zeta_omega_proof; // [Wzeta*omega]
+ uint256 private constant proof_opening_at_zeta_omega_x = 0x300;
+ uint256 private constant proof_opening_at_zeta_omega_y = 0x320;
+
+ uint256 private constant proof_openings_selector_commit_api_at_zeta = 0x340;
+ // -> next part of proof is
+ // [ openings_selector_commits || commitments_wires_commit_api]
+
+ // -------- offset state
+
+ // challenges to check the claimed quotient
+ uint256 private constant state_alpha = 0x00;
+ uint256 private constant state_beta = 0x20;
+ uint256 private constant state_gamma = 0x40;
+ uint256 private constant state_zeta = 0x60;
+
+ // reusable value
+ uint256 private constant state_alpha_square_lagrange_0 = 0x80;
+
+ // commitment to H
+ uint256 private constant state_folded_h_x = 0xa0;
+ uint256 private constant state_folded_h_y = 0xc0;
+
+ // commitment to the linearised polynomial
+ uint256 private constant state_linearised_polynomial_x = 0xe0;
+ uint256 private constant state_linearised_polynomial_y = 0x100;
+
+ // Folded proof for the opening of H, linearised poly, l, r, o, s_1, s_2, qcp
+ uint256 private constant state_folded_claimed_values = 0x120;
+
+ // folded digests of H, linearised poly, l, r, o, s_1, s_2, qcp
+ // Bn254.G1Point folded_digests;
+ uint256 private constant state_folded_digests_x = 0x140;
+ uint256 private constant state_folded_digests_y = 0x160;
+
+ uint256 private constant state_pi = 0x180;
+
+ uint256 private constant state_zeta_power_n_minus_one = 0x1a0;
+
+ uint256 private constant state_gamma_kzg = 0x1c0;
+
+ uint256 private constant state_success = 0x1e0;
+ uint256 private constant state_check_var = 0x200; // /!\ this slot is used for debugging only
+
+ uint256 private constant state_last_mem = 0x220;
+
+ // -------- errors
+ uint256 private constant error_string_id = 0x08c379a000000000000000000000000000000000000000000000000000000000; // selector for function Error(string)
+
+ // -------- utils (for hash_fr)
+ uint256 private constant bb = 340282366920938463463374607431768211456; // 2**128
+ uint256 private constant zero_uint256 = 0;
+
+ uint8 private constant lenInBytes = 48;
+ uint8 private constant sizeDomain = 11;
+ uint8 private constant one = 1;
+ uint8 private constant two = 2;
+
+ function Verify(bytes calldata proof, uint256[] calldata public_inputs) public view returns (bool success) {
+ assembly {
+ let mem := mload(0x40)
+ let freeMem := add(mem, state_last_mem)
+
+ // sanity checks
+ check_inputs_size(public_inputs.length, public_inputs.offset)
+ check_proof_size(proof.length)
+ check_proof_openings_size(proof.offset)
+
+ // compute the challenges
+ let prev_challenge_non_reduced
+ prev_challenge_non_reduced := derive_gamma(proof.offset, public_inputs.length, public_inputs.offset)
+ prev_challenge_non_reduced := derive_beta(prev_challenge_non_reduced)
+ prev_challenge_non_reduced := derive_alpha(proof.offset, prev_challenge_non_reduced)
+ derive_zeta(proof.offset, prev_challenge_non_reduced)
+
+ // evaluation of Z=Xâŋ-1 at Îļ, we save this value
+ let zeta := mload(add(mem, state_zeta))
+ let zeta_power_n_minus_one := addmod(pow(zeta, vk_domain_size, freeMem), sub(r_mod, 1), r_mod)
+ mstore(add(mem, state_zeta_power_n_minus_one), zeta_power_n_minus_one)
+
+ // public inputs contribution
+ let l_pi := sum_pi_wo_api_commit(public_inputs.offset, public_inputs.length, freeMem)
+ let l_wocommit := sum_pi_commit(proof.offset, public_inputs.length, freeMem)
+ l_pi := addmod(l_wocommit, l_pi, r_mod)
+ mstore(add(mem, state_pi), l_pi)
+
+ compute_alpha_square_lagrange_0()
+ verify_quotient_poly_eval_at_zeta(proof.offset)
+ fold_h(proof.offset)
+ compute_commitment_linearised_polynomial(proof.offset)
+ compute_gamma_kzg(proof.offset)
+ fold_state(proof.offset)
+ batch_verify_multi_points(proof.offset)
+
+ success := mload(add(mem, state_success))
+
+ // Beginning errors -------------------------------------------------
+ function error_ec_op() {
+ let ptError := mload(0x40)
+ mstore(ptError, error_string_id) // selector for function Error(string)
+ mstore(add(ptError, 0x4), 0x20)
+ mstore(add(ptError, 0x24), 0x12)
+ mstore(add(ptError, 0x44), "error ec operation")
+ revert(ptError, 0x64)
+ }
+
+ function error_inputs_size() {
+ let ptError := mload(0x40)
+ mstore(ptError, error_string_id) // selector for function Error(string)
+ mstore(add(ptError, 0x4), 0x20)
+ mstore(add(ptError, 0x24), 0x18)
+ mstore(add(ptError, 0x44), "inputs are bigger than r")
+ revert(ptError, 0x64)
+ }
+
+ function error_proof_size() {
+ let ptError := mload(0x40)
+ mstore(ptError, error_string_id) // selector for function Error(string)
+ mstore(add(ptError, 0x4), 0x20)
+ mstore(add(ptError, 0x24), 0x10)
+ mstore(add(ptError, 0x44), "wrong proof size")
+ revert(ptError, 0x64)
+ }
+
+ function error_proof_openings_size() {
+ let ptError := mload(0x40)
+ mstore(ptError, error_string_id) // selector for function Error(string)
+ mstore(add(ptError, 0x4), 0x20)
+ mstore(add(ptError, 0x24), 0x16)
+ mstore(add(ptError, 0x44), "openings bigger than r")
+ revert(ptError, 0x64)
+ }
+
+ function error_verify() {
+ let ptError := mload(0x40)
+ mstore(ptError, error_string_id) // selector for function Error(string)
+ mstore(add(ptError, 0x4), 0x20)
+ mstore(add(ptError, 0x24), 0xc)
+ mstore(add(ptError, 0x44), "error verify")
+ revert(ptError, 0x64)
+ }
+ // end errors -------------------------------------------------
+
+ // Beginning checks -------------------------------------------------
+
+ // s number of public inputs, p pointer the public inputs
+ function check_inputs_size(s, p) {
+ let input_checks := 1
+ for {
+ let i
+ } lt(i, s) {
+ i := add(i, 1)
+ } {
+ input_checks := and(input_checks, lt(calldataload(p), r_mod))
+ p := add(p, 0x20)
+ }
+ if iszero(input_checks) {
+ error_inputs_size()
+ }
+ }
+
+ function check_proof_size(actual_proof_size) {
+ let expected_proof_size := add(0x340, mul(vk_nb_commitments_commit_api, 0x60))
+ if iszero(eq(actual_proof_size, expected_proof_size)) {
+ error_proof_size()
+ }
+ }
+
+ function check_proof_openings_size(aproof) {
+ let openings_check := 1
+
+ // linearised polynomial at zeta
+ let p := add(aproof, proof_linearised_polynomial_at_zeta)
+ openings_check := and(openings_check, lt(calldataload(p), r_mod))
+
+ // quotient polynomial at zeta
+ p := add(aproof, proof_quotient_polynomial_at_zeta)
+ openings_check := and(openings_check, lt(calldataload(p), r_mod))
+
+ // proof_l_at_zeta
+ p := add(aproof, proof_l_at_zeta)
+ openings_check := and(openings_check, lt(calldataload(p), r_mod))
+
+ // proof_r_at_zeta
+ p := add(aproof, proof_r_at_zeta)
+ openings_check := and(openings_check, lt(calldataload(p), r_mod))
+
+ // proof_o_at_zeta
+ p := add(aproof, proof_o_at_zeta)
+ openings_check := and(openings_check, lt(calldataload(p), r_mod))
+
+ // proof_s1_at_zeta
+ p := add(aproof, proof_s1_at_zeta)
+ openings_check := and(openings_check, lt(calldataload(p), r_mod))
+
+ // proof_s2_at_zeta
+ p := add(aproof, proof_s2_at_zeta)
+ openings_check := and(openings_check, lt(calldataload(p), r_mod))
+
+ // proof_grand_product_at_zeta_omega
+ p := add(aproof, proof_grand_product_at_zeta_omega)
+ openings_check := and(openings_check, lt(calldataload(p), r_mod))
+
+ // proof_openings_selector_commit_api_at_zeta
+
+ p := add(aproof, proof_openings_selector_commit_api_at_zeta)
+ for {
+ let i := 0
+ } lt(i, vk_nb_commitments_commit_api) {
+ i := add(i, 1)
+ } {
+ openings_check := and(openings_check, lt(calldataload(p), r_mod))
+ p := add(p, 0x20)
+ }
+
+ if iszero(openings_check) {
+ error_proof_openings_size()
+ }
+ }
+ // end checks -------------------------------------------------
+
+ // Beginning challenges -------------------------------------------------
+
+ // Derive gamma as Sha256()
+ // where transcript is the concatenation (in this order) of:
+ // * the word "gamma" in ascii, equal to [0x67,0x61,0x6d, 0x6d, 0x61] and encoded as a uint256.
+ // * the commitments to the permutation polynomials S1, S2, S3, where we concatenate the coordinates of those points
+ // * the commitments of Ql, Qr, Qm, Qo, Qk
+ // * the public inputs
+ // * the commitments of the wires related to the custom gates (commitments_wires_commit_api)
+ // * commitments to L, R, O (proof__com_)
+ // The data described above is written starting at mPtr. "gamma" lies on 5 bytes,
+ // and is encoded as a uint256 number n. In basis b = 256, the number looks like this
+ // [0 0 0 .. 0x67 0x61 0x6d, 0x6d, 0x61]. The first non zero entry is at position 27=0x1b
+ // nb_pi, pi respectively number of public inputs and public inputs
+ function derive_gamma(aproof, nb_pi, pi) -> gamma_not_reduced {
+ let state := mload(0x40)
+ let mPtr := add(state, state_last_mem)
+
+ // gamma
+ // gamma in ascii is [0x67,0x61,0x6d, 0x6d, 0x61]
+ // (same for alpha, beta, zeta)
+ mstore(mPtr, 0x67616d6d61) // "gamma"
+
+ mstore(add(mPtr, 0x20), vk_s1_com_x)
+ mstore(add(mPtr, 0x40), vk_s1_com_y)
+ mstore(add(mPtr, 0x60), vk_s2_com_x)
+ mstore(add(mPtr, 0x80), vk_s2_com_y)
+ mstore(add(mPtr, 0xa0), vk_s3_com_x)
+ mstore(add(mPtr, 0xc0), vk_s3_com_y)
+ mstore(add(mPtr, 0xe0), vk_ql_com_x)
+ mstore(add(mPtr, 0x100), vk_ql_com_y)
+ mstore(add(mPtr, 0x120), vk_qr_com_x)
+ mstore(add(mPtr, 0x140), vk_qr_com_y)
+ mstore(add(mPtr, 0x160), vk_qm_com_x)
+ mstore(add(mPtr, 0x180), vk_qm_com_y)
+ mstore(add(mPtr, 0x1a0), vk_qo_com_x)
+ mstore(add(mPtr, 0x1c0), vk_qo_com_y)
+ mstore(add(mPtr, 0x1e0), vk_qk_com_x)
+ mstore(add(mPtr, 0x200), vk_qk_com_y)
+
+ // public inputs
+ let _mPtr := add(mPtr, 0x220)
+ let size_pi_in_bytes := mul(nb_pi, 0x20)
+ calldatacopy(_mPtr, pi, size_pi_in_bytes)
+ _mPtr := add(_mPtr, size_pi_in_bytes)
+
+ // wire commitment commit api
+ let _proof := add(aproof, proof_openings_selector_commit_api_at_zeta)
+ _proof := add(_proof, mul(vk_nb_commitments_commit_api, 0x20))
+ let size_wire_commitments_commit_api_in_bytes := mul(vk_nb_commitments_commit_api, 0x40)
+ calldatacopy(_mPtr, _proof, size_wire_commitments_commit_api_in_bytes)
+ _mPtr := add(_mPtr, size_wire_commitments_commit_api_in_bytes)
+
+ // commitments to l, r, o
+ let size_commitments_lro_in_bytes := 0xc0
+ calldatacopy(_mPtr, aproof, size_commitments_lro_in_bytes)
+ _mPtr := add(_mPtr, size_commitments_lro_in_bytes)
+
+ let size := add(0x2c5, mul(nb_pi, 0x20)) // 0x2c5 = 22*32+5
+ size := add(size, mul(vk_nb_commitments_commit_api, 0x40))
+ let l_success := staticcall(gas(), 0x2, add(mPtr, 0x1b), size, mPtr, 0x20) //0x1b -> 000.."gamma"
+ if iszero(l_success) {
+ error_verify()
+ }
+ gamma_not_reduced := mload(mPtr)
+ mstore(add(state, state_gamma), mod(gamma_not_reduced, r_mod))
+ }
+
+ function derive_beta(gamma_not_reduced) -> beta_not_reduced {
+ let state := mload(0x40)
+ let mPtr := add(mload(0x40), state_last_mem)
+
+ // beta
+ mstore(mPtr, 0x62657461) // "beta"
+ mstore(add(mPtr, 0x20), gamma_not_reduced)
+ let l_success := staticcall(gas(), 0x2, add(mPtr, 0x1c), 0x24, mPtr, 0x20) //0x1b -> 000.."gamma"
+ if iszero(l_success) {
+ error_verify()
+ }
+ beta_not_reduced := mload(mPtr)
+ mstore(add(state, state_beta), mod(beta_not_reduced, r_mod))
+ }
+
+ // alpha depends on the previous challenge (beta) and on the commitment to the grand product polynomial
+ function derive_alpha(aproof, beta_not_reduced) -> alpha_not_reduced {
+ let state := mload(0x40)
+ let mPtr := add(mload(0x40), state_last_mem)
+
+ // alpha
+ mstore(mPtr, 0x616C706861) // "alpha"
+ mstore(add(mPtr, 0x20), beta_not_reduced)
+ calldatacopy(add(mPtr, 0x40), add(aproof, proof_grand_product_commitment_x), 0x40)
+ let l_success := staticcall(gas(), 0x2, add(mPtr, 0x1b), 0x65, mPtr, 0x20) //0x1b -> 000.."gamma"
+ if iszero(l_success) {
+ error_verify()
+ }
+ alpha_not_reduced := mload(mPtr)
+ mstore(add(state, state_alpha), mod(alpha_not_reduced, r_mod))
+ }
+
+ // zeta depends on the previous challenge (alpha) and on the commitment to the quotient polynomial
+ function derive_zeta(aproof, alpha_not_reduced) {
+ let state := mload(0x40)
+ let mPtr := add(mload(0x40), state_last_mem)
+
+ // zeta
+ mstore(mPtr, 0x7a657461) // "zeta"
+ mstore(add(mPtr, 0x20), alpha_not_reduced)
+ calldatacopy(add(mPtr, 0x40), add(aproof, proof_h_0_x), 0xc0)
+ let l_success := staticcall(gas(), 0x2, add(mPtr, 0x1c), 0xe4, mPtr, 0x20)
+ if iszero(l_success) {
+ error_verify()
+ }
+ let zeta_not_reduced := mload(mPtr)
+ mstore(add(state, state_zeta), mod(zeta_not_reduced, r_mod))
+ }
+ // END challenges -------------------------------------------------
+
+ // BEGINNING compute_pi -------------------------------------------------
+
+ // public input (not comming from the commit api) contribution
+ // ins, n are the public inputs and number of public inputs respectively
+ function sum_pi_wo_api_commit(ins, n, mPtr) -> pi_wo_commit {
+ let state := mload(0x40)
+ let z := mload(add(state, state_zeta))
+ let zpnmo := mload(add(state, state_zeta_power_n_minus_one))
+
+ let li := mPtr
+ batch_compute_lagranges_at_z(z, zpnmo, n, li)
+
+ let tmp := 0
+ for {
+ let i := 0
+ } lt(i, n) {
+ i := add(i, 1)
+ } {
+ tmp := mulmod(mload(li), calldataload(ins), r_mod)
+ pi_wo_commit := addmod(pi_wo_commit, tmp, r_mod)
+ li := add(li, 0x20)
+ ins := add(ins, 0x20)
+ }
+ }
+
+ // mPtr <- [L_0(z), .., L_{n-1}(z)]
+ //
+ // Here L_i(zeta) = Īâą/n * (Îļâŋ-1)/(Îļ-Īâą) where:
+ // * n = vk_domain_size
+ // * Ī = vk_omega (generator of the multiplicative cyclic group of order n in (â¤/râ¤)*)
+ // * Îļ = z (challenge derived with Fiat Shamir)
+ // * zpnmo = 'zeta power n minus one' (Îļâŋ-1) which has been precomputed
+ function batch_compute_lagranges_at_z(z, zpnmo, n, mPtr) {
+ let zn := mulmod(zpnmo, vk_inv_domain_size, r_mod) // 1/n * (Îļâŋ - 1)
+
+ let _w := 1
+ let _mPtr := mPtr
+ for {
+ let i := 0
+ } lt(i, n) {
+ i := add(i, 1)
+ } {
+ mstore(_mPtr, addmod(z, sub(r_mod, _w), r_mod))
+ _w := mulmod(_w, vk_omega, r_mod)
+ _mPtr := add(_mPtr, 0x20)
+ }
+ batch_invert(mPtr, n, _mPtr)
+ _mPtr := mPtr
+ _w := 1
+ for {
+ let i := 0
+ } lt(i, n) {
+ i := add(i, 1)
+ } {
+ mstore(_mPtr, mulmod(mulmod(mload(_mPtr), zn, r_mod), _w, r_mod))
+ _mPtr := add(_mPtr, 0x20)
+ _w := mulmod(_w, vk_omega, r_mod)
+ }
+ }
+
+ // batch invert (modulo r) in place the nb_ins uint256 inputs starting at ins.
+ function batch_invert(ins, nb_ins, mPtr) {
+ mstore(mPtr, 1)
+ let offset := 0
+ for {
+ let i := 0
+ } lt(i, nb_ins) {
+ i := add(i, 1)
+ } {
+ let prev := mload(add(mPtr, offset))
+ let cur := mload(add(ins, offset))
+ cur := mulmod(prev, cur, r_mod)
+ offset := add(offset, 0x20)
+ mstore(add(mPtr, offset), cur)
+ }
+ ins := add(ins, sub(offset, 0x20))
+ mPtr := add(mPtr, offset)
+ let inv := pow(mload(mPtr), sub(r_mod, 2), add(mPtr, 0x20))
+ for {
+ let i := 0
+ } lt(i, nb_ins) {
+ i := add(i, 1)
+ } {
+ mPtr := sub(mPtr, 0x20)
+ let tmp := mload(ins)
+ let cur := mulmod(inv, mload(mPtr), r_mod)
+ mstore(ins, cur)
+ inv := mulmod(inv, tmp, r_mod)
+ ins := sub(ins, 0x20)
+ }
+ }
+
+ // mPtr free memory. Computes the public input contribution related to the commit
+ function sum_pi_commit(aproof, nb_public_inputs, mPtr) -> pi_commit {
+ let state := mload(0x40)
+ let z := mload(add(state, state_zeta))
+ let zpnmo := mload(add(state, state_zeta_power_n_minus_one))
+
+ let p := add(aproof, proof_openings_selector_commit_api_at_zeta)
+ p := add(p, mul(vk_nb_commitments_commit_api, 0x20)) // p points now to the wire commitments
+
+ let h_fr, ith_lagrange
+
+ h_fr := hash_fr(calldataload(p), calldataload(add(p, 0x20)), mPtr)
+ ith_lagrange := compute_ith_lagrange_at_z(z, zpnmo, add(nb_public_inputs, vk_index_commit_api_0), mPtr)
+ pi_commit := addmod(pi_commit, mulmod(h_fr, ith_lagrange, r_mod), r_mod)
+ p := add(p, 0x40)
+ }
+
+ // z zeta
+ // zpmno Îļâŋ-1
+ // i i-th lagrange
+ // mPtr free memory
+ // Computes L_i(zeta) = Īâą/n * (Îļâŋ-1)/(Îļ-Īâą) where:
+ function compute_ith_lagrange_at_z(z, zpnmo, i, mPtr) -> res {
+ let w := pow(vk_omega, i, mPtr) // w**i
+ i := addmod(z, sub(r_mod, w), r_mod) // z-w**i
+ w := mulmod(w, vk_inv_domain_size, r_mod) // w**i/n
+ i := pow(i, sub(r_mod, 2), mPtr) // (z-w**i)**-1
+ w := mulmod(w, i, r_mod) // w**i/n*(z-w)**-1
+ res := mulmod(w, zpnmo, r_mod)
+ }
+
+ // (x, y) point on bn254, both on 32bytes
+ // mPtr free memory
+ function hash_fr(x, y, mPtr) -> res {
+ // [0x00, .. , 0x00 || x, y, || 0, 48, 0, dst, sizeDomain]
+ // <- 64 bytes -> <-64b -> <- 1 bytes each ->
+
+ // [0x00, .., 0x00] 64 bytes of zero
+ mstore(mPtr, zero_uint256)
+ mstore(add(mPtr, 0x20), zero_uint256)
+
+ // msg = x || y , both on 32 bytes
+ mstore(add(mPtr, 0x40), x)
+ mstore(add(mPtr, 0x60), y)
+
+ // 0 || 48 || 0 all on 1 byte
+ mstore8(add(mPtr, 0x80), 0)
+ mstore8(add(mPtr, 0x81), lenInBytes)
+ mstore8(add(mPtr, 0x82), 0)
+
+ // "BSB22-Plonk" = [42, 53, 42, 32, 32, 2d, 50, 6c, 6f, 6e, 6b,]
+ mstore8(add(mPtr, 0x83), 0x42)
+ mstore8(add(mPtr, 0x84), 0x53)
+ mstore8(add(mPtr, 0x85), 0x42)
+ mstore8(add(mPtr, 0x86), 0x32)
+ mstore8(add(mPtr, 0x87), 0x32)
+ mstore8(add(mPtr, 0x88), 0x2d)
+ mstore8(add(mPtr, 0x89), 0x50)
+ mstore8(add(mPtr, 0x8a), 0x6c)
+ mstore8(add(mPtr, 0x8b), 0x6f)
+ mstore8(add(mPtr, 0x8c), 0x6e)
+ mstore8(add(mPtr, 0x8d), 0x6b)
+
+ // size domain
+ mstore8(add(mPtr, 0x8e), sizeDomain)
+
+ let l_success := staticcall(gas(), 0x2, mPtr, 0x8f, mPtr, 0x20)
+ if iszero(l_success) {
+ error_verify()
+ }
+
+ let b0 := mload(mPtr)
+
+ // [b0 || one || dst || sizeDomain]
+ // <-64bytes -> <- 1 byte each ->
+ mstore8(add(mPtr, 0x20), one) // 1
+
+ mstore8(add(mPtr, 0x21), 0x42) // dst
+ mstore8(add(mPtr, 0x22), 0x53)
+ mstore8(add(mPtr, 0x23), 0x42)
+ mstore8(add(mPtr, 0x24), 0x32)
+ mstore8(add(mPtr, 0x25), 0x32)
+ mstore8(add(mPtr, 0x26), 0x2d)
+ mstore8(add(mPtr, 0x27), 0x50)
+ mstore8(add(mPtr, 0x28), 0x6c)
+ mstore8(add(mPtr, 0x29), 0x6f)
+ mstore8(add(mPtr, 0x2a), 0x6e)
+ mstore8(add(mPtr, 0x2b), 0x6b)
+
+ mstore8(add(mPtr, 0x2c), sizeDomain) // size domain
+ l_success := staticcall(gas(), 0x2, mPtr, 0x2d, mPtr, 0x20)
+ if iszero(l_success) {
+ error_verify()
+ }
+
+ // b1 is located at mPtr. We store b2 at add(mPtr, 0x20)
+
+ // [b0^b1 || two || dst || sizeDomain]
+ // <-64bytes -> <- 1 byte each ->
+ mstore(add(mPtr, 0x20), xor(mload(mPtr), b0))
+ mstore8(add(mPtr, 0x40), two)
+
+ mstore8(add(mPtr, 0x41), 0x42) // dst
+ mstore8(add(mPtr, 0x42), 0x53)
+ mstore8(add(mPtr, 0x43), 0x42)
+ mstore8(add(mPtr, 0x44), 0x32)
+ mstore8(add(mPtr, 0x45), 0x32)
+ mstore8(add(mPtr, 0x46), 0x2d)
+ mstore8(add(mPtr, 0x47), 0x50)
+ mstore8(add(mPtr, 0x48), 0x6c)
+ mstore8(add(mPtr, 0x49), 0x6f)
+ mstore8(add(mPtr, 0x4a), 0x6e)
+ mstore8(add(mPtr, 0x4b), 0x6b)
+
+ mstore8(add(mPtr, 0x4c), sizeDomain) // size domain
+
+ let offset := add(mPtr, 0x20)
+ l_success := staticcall(gas(), 0x2, offset, 0x2d, offset, 0x20)
+ if iszero(l_success) {
+ error_verify()
+ }
+
+ // at this point we have mPtr = [ b1 || b2] where b1 is on 32byes and b2 in 16bytes.
+ // we interpret it as a big integer mod r in big endian (similar to regular decimal notation)
+ // the result is then 2**(8*16)*mPtr[32:] + mPtr[32:48]
+ res := mulmod(mload(mPtr), bb, r_mod) // <- res = 2**128 * mPtr[:32]
+ offset := add(mPtr, 0x10)
+ for {
+ let i := 0
+ } lt(i, 0x10) {
+ i := add(i, 1)
+ } {
+ // mPtr <- [xx, xx, .., | 0, 0, .. 0 || b2 ]
+ mstore8(offset, 0x00)
+ offset := add(offset, 0x1)
+ }
+ let b1 := mload(add(mPtr, 0x10)) // b1 <- [0, 0, .., 0 || b2[:16] ]
+ res := addmod(res, b1, r_mod)
+ }
+
+ // END compute_pi -------------------------------------------------
+
+ // compute ι² * 1/n * (Îļ{n}-1)/(Îļ - 1) where
+ // * Îą = challenge derived in derive_gamma_beta_alpha_zeta
+ // * n = vk_domain_size
+ // * Ī = vk_omega (generator of the multiplicative cyclic group of order n in (â¤/râ¤)*)
+ // * Îļ = zeta (challenge derived with Fiat Shamir)
+ function compute_alpha_square_lagrange_0() {
+ let state := mload(0x40)
+ let mPtr := add(mload(0x40), state_last_mem)
+
+ let res := mload(add(state, state_zeta_power_n_minus_one))
+ let den := addmod(mload(add(state, state_zeta)), sub(r_mod, 1), r_mod)
+ den := pow(den, sub(r_mod, 2), mPtr)
+ den := mulmod(den, vk_inv_domain_size, r_mod)
+ res := mulmod(den, res, r_mod)
+
+ let l_alpha := mload(add(state, state_alpha))
+ res := mulmod(res, l_alpha, r_mod)
+ res := mulmod(res, l_alpha, r_mod)
+ mstore(add(state, state_alpha_square_lagrange_0), res)
+ }
+
+ // follows alg. p.13 of https://eprint.iacr.org/2019/953.pdf
+ // with tâ = tâ = 1, and the proofs are ([digest] + [quotient] +purported evaluation):
+ // * [state_folded_state_digests], [proof_batch_opening_at_zeta_x], state_folded_evals
+ // * [proof_grand_product_commitment], [proof_opening_at_zeta_omega_x], [proof_grand_product_at_zeta_omega]
+ function batch_verify_multi_points(aproof) {
+ let state := mload(0x40)
+ let mPtr := add(state, state_last_mem)
+
+ // here the random is not a challenge, hence no need to use Fiat Shamir, we just
+ // need an unpredictible result.
+ let random := mod(keccak256(state, 0x20), r_mod)
+
+ let folded_quotients := mPtr
+ mPtr := add(folded_quotients, 0x40)
+ mstore(folded_quotients, calldataload(add(aproof, proof_batch_opening_at_zeta_x)))
+ mstore(add(folded_quotients, 0x20), calldataload(add(aproof, proof_batch_opening_at_zeta_y)))
+ point_acc_mul_calldata(folded_quotients, add(aproof, proof_opening_at_zeta_omega_x), random, mPtr)
+
+ let folded_digests := add(state, state_folded_digests_x)
+ point_acc_mul_calldata(folded_digests, add(aproof, proof_grand_product_commitment_x), random, mPtr)
+
+ let folded_evals := add(state, state_folded_claimed_values)
+ fr_acc_mul_calldata(folded_evals, add(aproof, proof_grand_product_at_zeta_omega), random)
+
+ let folded_evals_commit := mPtr
+ mPtr := add(folded_evals_commit, 0x40)
+ mstore(folded_evals_commit, 14312776538779914388377568895031746459131577658076416373430523308756343304251)
+ mstore(
+ add(folded_evals_commit, 0x20),
+ 11763105256161367503191792604679297387056316997144156930871823008787082098465
+ )
+ mstore(add(folded_evals_commit, 0x40), mload(folded_evals))
+ let check_staticcall := staticcall(gas(), 7, folded_evals_commit, 0x60, folded_evals_commit, 0x40)
+ if eq(check_staticcall, 0) {
+ error_verify()
+ }
+
+ let folded_evals_commit_y := add(folded_evals_commit, 0x20)
+ mstore(folded_evals_commit_y, sub(p_mod, mload(folded_evals_commit_y)))
+ point_add(folded_digests, folded_digests, folded_evals_commit, mPtr)
+
+ let folded_points_quotients := mPtr
+ mPtr := add(mPtr, 0x40)
+ point_mul_calldata(
+ folded_points_quotients,
+ add(aproof, proof_batch_opening_at_zeta_x),
+ mload(add(state, state_zeta)),
+ mPtr
+ )
+ let zeta_omega := mulmod(mload(add(state, state_zeta)), vk_omega, r_mod)
+ random := mulmod(random, zeta_omega, r_mod)
+ point_acc_mul_calldata(folded_points_quotients, add(aproof, proof_opening_at_zeta_omega_x), random, mPtr)
+
+ point_add(folded_digests, folded_digests, folded_points_quotients, mPtr)
+
+ let folded_quotients_y := add(folded_quotients, 0x20)
+ mstore(folded_quotients_y, sub(p_mod, mload(folded_quotients_y)))
+
+ mstore(mPtr, mload(folded_digests))
+ mstore(add(mPtr, 0x20), mload(add(folded_digests, 0x20)))
+ mstore(add(mPtr, 0x40), g2_srs_0_x_0) // the 4 lines are the canonical G2 point on BN254
+ mstore(add(mPtr, 0x60), g2_srs_0_x_1)
+ mstore(add(mPtr, 0x80), g2_srs_0_y_0)
+ mstore(add(mPtr, 0xa0), g2_srs_0_y_1)
+ mstore(add(mPtr, 0xc0), mload(folded_quotients))
+ mstore(add(mPtr, 0xe0), mload(add(folded_quotients, 0x20)))
+ mstore(add(mPtr, 0x100), g2_srs_1_x_0)
+ mstore(add(mPtr, 0x120), g2_srs_1_x_1)
+ mstore(add(mPtr, 0x140), g2_srs_1_y_0)
+ mstore(add(mPtr, 0x160), g2_srs_1_y_1)
+ check_pairing_kzg(mPtr)
+ }
+
+ // check_pairing_kzg checks the result of the final pairing product of the batched
+ // kzg verification. The purpose of this function is too avoid exhausting the stack
+ // in the function batch_verify_multi_points.
+ // mPtr: pointer storing the tuple of pairs
+ function check_pairing_kzg(mPtr) {
+ let state := mload(0x40)
+
+ // TODO test the staticcall using the method from audit_4-5
+ let l_success := staticcall(gas(), 8, mPtr, 0x180, 0x00, 0x20)
+ let res_pairing := mload(0x00)
+ let s_success := mload(add(state, state_success))
+ res_pairing := and(and(res_pairing, l_success), s_success)
+ mstore(add(state, state_success), res_pairing)
+ }
+
+ // Fold the opening proofs at Îļ:
+ // * at state+state_folded_digest we store: [H] + Îŗ[Linearised_polynomial]+Îŗ²[L] + ÎŗÂŗ[R] + Îŗâ´[O] + Îŗâĩ[Sâ] +Îŗâļ[Sâ] + âáĩĸÎŗâļâēâą[Pi_{i}]
+ // * at state+state_folded_claimed_values we store: H(Îļ) + ÎŗLinearised_polynomial(Îļ)+Îŗ²L(Îļ) + ÎŗÂŗR(Îļ)+ Îŗâ´O(Îļ) + ÎŗâĩSâ(Îļ) +ÎŗâļSâ(Îļ) + âáĩĸÎŗâļâēâąPi_{i}(Îļ)
+ // acc_gamma stores the Îŗâą
+ function fold_state(aproof) {
+ let state := mload(0x40)
+ let mPtr := add(mload(0x40), state_last_mem)
+
+ let l_gamma_kzg := mload(add(state, state_gamma_kzg))
+ let acc_gamma := l_gamma_kzg
+
+ let offset := add(0x200, mul(vk_nb_commitments_commit_api, 0x40)) // 0x40 = 2*0x20
+ let mPtrOffset := add(mPtr, offset)
+
+ mstore(add(state, state_folded_digests_x), mload(add(mPtr, 0x40)))
+ mstore(add(state, state_folded_digests_y), mload(add(mPtr, 0x60)))
+ mstore(add(state, state_folded_claimed_values), calldataload(add(aproof, proof_quotient_polynomial_at_zeta)))
+
+ point_acc_mul(add(state, state_folded_digests_x), add(mPtr, 0x80), acc_gamma, mPtrOffset)
+ fr_acc_mul_calldata(
+ add(state, state_folded_claimed_values),
+ add(aproof, proof_linearised_polynomial_at_zeta),
+ acc_gamma
+ )
+
+ acc_gamma := mulmod(acc_gamma, l_gamma_kzg, r_mod)
+ point_acc_mul(add(state, state_folded_digests_x), add(mPtr, 0xc0), acc_gamma, mPtrOffset)
+ fr_acc_mul_calldata(add(state, state_folded_claimed_values), add(aproof, proof_l_at_zeta), acc_gamma)
+
+ acc_gamma := mulmod(acc_gamma, l_gamma_kzg, r_mod)
+ point_acc_mul(add(state, state_folded_digests_x), add(mPtr, 0x100), acc_gamma, add(mPtr, offset))
+ fr_acc_mul_calldata(add(state, state_folded_claimed_values), add(aproof, proof_r_at_zeta), acc_gamma)
+
+ acc_gamma := mulmod(acc_gamma, l_gamma_kzg, r_mod)
+ point_acc_mul(add(state, state_folded_digests_x), add(mPtr, 0x140), acc_gamma, add(mPtr, offset))
+ fr_acc_mul_calldata(add(state, state_folded_claimed_values), add(aproof, proof_o_at_zeta), acc_gamma)
+
+ acc_gamma := mulmod(acc_gamma, l_gamma_kzg, r_mod)
+ point_acc_mul(add(state, state_folded_digests_x), add(mPtr, 0x180), acc_gamma, add(mPtr, offset))
+ fr_acc_mul_calldata(add(state, state_folded_claimed_values), add(aproof, proof_s1_at_zeta), acc_gamma)
+
+ acc_gamma := mulmod(acc_gamma, l_gamma_kzg, r_mod)
+ point_acc_mul(add(state, state_folded_digests_x), add(mPtr, 0x1c0), acc_gamma, add(mPtr, offset))
+ fr_acc_mul_calldata(add(state, state_folded_claimed_values), add(aproof, proof_s2_at_zeta), acc_gamma)
+
+ let poscaz := add(aproof, proof_openings_selector_commit_api_at_zeta)
+ let opca := add(mPtr, 0x200) // offset_proof_commits_api
+ for {
+ let i := 0
+ } lt(i, vk_nb_commitments_commit_api) {
+ i := add(i, 1)
+ } {
+ acc_gamma := mulmod(acc_gamma, l_gamma_kzg, r_mod)
+ point_acc_mul(add(state, state_folded_digests_x), opca, acc_gamma, add(mPtr, offset))
+ fr_acc_mul_calldata(add(state, state_folded_claimed_values), poscaz, acc_gamma)
+ poscaz := add(poscaz, 0x20)
+ opca := add(opca, 0x40)
+ }
+ }
+
+ // generate the challenge (using Fiat Shamir) to fold the opening proofs
+ // at Îļ.
+ // The process for deriving Îŗ is the same as in derive_gamma but this time the inputs are
+ // in this order (the [] means it's a commitment):
+ // * Îļ
+ // * [H] ( = Hâ + Îļáĩâē²*Hâ + Îļ²âŊáĩâē²âž*Hâ )
+ // * [Linearised polynomial]
+ // * [L], [R], [O]
+ // * [Sâ] [Sâ]
+ // * [Pi_{i}] (wires associated to custom gates)
+ // Then there are the purported evaluations of the previous committed polynomials:
+ // * H(Îļ)
+ // * Linearised_polynomial(Îļ)
+ // * L(Îļ), R(Îļ), O(Îļ), Sâ(Îļ), Sâ(Îļ)
+ // * Pi_{i}(Îļ)
+ function compute_gamma_kzg(aproof) {
+ let state := mload(0x40)
+ let mPtr := add(mload(0x40), state_last_mem)
+ mstore(mPtr, 0x67616d6d61) // "gamma"
+ mstore(add(mPtr, 0x20), mload(add(state, state_zeta)))
+ mstore(add(mPtr, 0x40), mload(add(state, state_folded_h_x)))
+ mstore(add(mPtr, 0x60), mload(add(state, state_folded_h_y)))
+ mstore(add(mPtr, 0x80), mload(add(state, state_linearised_polynomial_x)))
+ mstore(add(mPtr, 0xa0), mload(add(state, state_linearised_polynomial_y)))
+ calldatacopy(add(mPtr, 0xc0), add(aproof, proof_l_com_x), 0xc0)
+ mstore(add(mPtr, 0x180), vk_s1_com_x)
+ mstore(add(mPtr, 0x1a0), vk_s1_com_y)
+ mstore(add(mPtr, 0x1c0), vk_s2_com_x)
+ mstore(add(mPtr, 0x1e0), vk_s2_com_y)
+
+ let offset := 0x200
+
+ mstore(add(mPtr, offset), vk_selector_commitments_commit_api_0_x)
+ mstore(add(mPtr, add(offset, 0x20)), vk_selector_commitments_commit_api_0_y)
+ offset := add(offset, 0x40)
+
+ mstore(add(mPtr, offset), calldataload(add(aproof, proof_quotient_polynomial_at_zeta)))
+ mstore(add(mPtr, add(offset, 0x20)), calldataload(add(aproof, proof_linearised_polynomial_at_zeta)))
+ mstore(add(mPtr, add(offset, 0x40)), calldataload(add(aproof, proof_l_at_zeta)))
+ mstore(add(mPtr, add(offset, 0x60)), calldataload(add(aproof, proof_r_at_zeta)))
+ mstore(add(mPtr, add(offset, 0x80)), calldataload(add(aproof, proof_o_at_zeta)))
+ mstore(add(mPtr, add(offset, 0xa0)), calldataload(add(aproof, proof_s1_at_zeta)))
+ mstore(add(mPtr, add(offset, 0xc0)), calldataload(add(aproof, proof_s2_at_zeta)))
+
+ let _mPtr := add(mPtr, add(offset, 0xe0))
+ let _poscaz := add(aproof, proof_openings_selector_commit_api_at_zeta)
+ for {
+ let i := 0
+ } lt(i, vk_nb_commitments_commit_api) {
+ i := add(i, 1)
+ } {
+ mstore(_mPtr, calldataload(_poscaz))
+ _poscaz := add(_poscaz, 0x20)
+ _mPtr := add(_mPtr, 0x20)
+ }
+
+ let start_input := 0x1b // 00.."gamma"
+ let size_input := add(0x16, mul(vk_nb_commitments_commit_api, 3)) // number of 32bytes elmts = 0x16 (zeta+2*7+7 for the digests+openings) + 2*vk_nb_commitments_commit_api (for the commitments of the selectors) + vk_nb_commitments_commit_api (for the openings of the selectors)
+ size_input := add(0x5, mul(size_input, 0x20)) // size in bytes: 15*32 bytes + 5 bytes for gamma
+ let check_staticcall := staticcall(
+ gas(),
+ 0x2,
+ add(mPtr, start_input),
+ size_input,
+ add(state, state_gamma_kzg),
+ 0x20
+ )
+ if eq(check_staticcall, 0) {
+ error_verify()
+ }
+ mstore(add(state, state_gamma_kzg), mod(mload(add(state, state_gamma_kzg)), r_mod))
+ }
+
+ function compute_commitment_linearised_polynomial_ec(aproof, s1, s2) {
+ let state := mload(0x40)
+ let mPtr := add(mload(0x40), state_last_mem)
+
+ mstore(mPtr, vk_ql_com_x)
+ mstore(add(mPtr, 0x20), vk_ql_com_y)
+ point_mul(
+ add(state, state_linearised_polynomial_x),
+ mPtr,
+ calldataload(add(aproof, proof_l_at_zeta)),
+ add(mPtr, 0x40)
+ )
+
+ mstore(mPtr, vk_qr_com_x)
+ mstore(add(mPtr, 0x20), vk_qr_com_y)
+ point_acc_mul(
+ add(state, state_linearised_polynomial_x),
+ mPtr,
+ calldataload(add(aproof, proof_r_at_zeta)),
+ add(mPtr, 0x40)
+ )
+
+ let rl := mulmod(calldataload(add(aproof, proof_l_at_zeta)), calldataload(add(aproof, proof_r_at_zeta)), r_mod)
+ mstore(mPtr, vk_qm_com_x)
+ mstore(add(mPtr, 0x20), vk_qm_com_y)
+ point_acc_mul(add(state, state_linearised_polynomial_x), mPtr, rl, add(mPtr, 0x40))
+
+ mstore(mPtr, vk_qo_com_x)
+ mstore(add(mPtr, 0x20), vk_qo_com_y)
+ point_acc_mul(
+ add(state, state_linearised_polynomial_x),
+ mPtr,
+ calldataload(add(aproof, proof_o_at_zeta)),
+ add(mPtr, 0x40)
+ )
+
+ mstore(mPtr, vk_qk_com_x)
+ mstore(add(mPtr, 0x20), vk_qk_com_y)
+ point_add(
+ add(state, state_linearised_polynomial_x),
+ add(state, state_linearised_polynomial_x),
+ mPtr,
+ add(mPtr, 0x40)
+ )
+
+ let commits_api_at_zeta := add(aproof, proof_openings_selector_commit_api_at_zeta)
+ let commits_api := add(
+ aproof,
+ add(proof_openings_selector_commit_api_at_zeta, mul(vk_nb_commitments_commit_api, 0x20))
+ )
+ for {
+ let i := 0
+ } lt(i, vk_nb_commitments_commit_api) {
+ i := add(i, 1)
+ } {
+ mstore(mPtr, calldataload(commits_api))
+ mstore(add(mPtr, 0x20), calldataload(add(commits_api, 0x20)))
+ point_acc_mul(
+ add(state, state_linearised_polynomial_x),
+ mPtr,
+ calldataload(commits_api_at_zeta),
+ add(mPtr, 0x40)
+ )
+ commits_api_at_zeta := add(commits_api_at_zeta, 0x20)
+ commits_api := add(commits_api, 0x40)
+ }
+
+ mstore(mPtr, vk_s3_com_x)
+ mstore(add(mPtr, 0x20), vk_s3_com_y)
+ point_acc_mul(add(state, state_linearised_polynomial_x), mPtr, s1, add(mPtr, 0x40))
+
+ mstore(mPtr, calldataload(add(aproof, proof_grand_product_commitment_x)))
+ mstore(add(mPtr, 0x20), calldataload(add(aproof, proof_grand_product_commitment_y)))
+ point_acc_mul(add(state, state_linearised_polynomial_x), mPtr, s2, add(mPtr, 0x40))
+ }
+
+ // Compute the commitment to the linearized polynomial equal to
+ // L(Îļ)[Qâ]+r(Îļ)[QáĩŖ]+R(Îļ)L(Îļ)[Qâ]+O(Îļ)[Qâ]+[Qâ]+ÎŖáĩĸqc'áĩĸ(Îļ)[BsbCommitmentáĩĸ] +
+ // Îą*( Z(ÎŧÎļ)(L(Îļ)+β*Sâ(Îļ)+Îŗ)*(R(Îļ)+β*Sâ(Îļ)+Îŗ)[Sâ]-[Z](L(Îļ)+β*id_{1}(Îļ)+Îŗ)*(R(Îļ)+β*id_{2(Îļ)+Îŗ)*(O(Îļ)+β*id_{3}(Îļ)+Îŗ) ) +
+ // ι²*Lâ(Îļ)[Z]
+ // where
+ // * id_1 = id, id_2 = vk_coset_shift*id, id_3 = vk_coset_shift^{2}*id
+ // * the [] means that it's a commitment (i.e. a point on Bn254(F_p))
+ function compute_commitment_linearised_polynomial(aproof) {
+ let state := mload(0x40)
+ let l_beta := mload(add(state, state_beta))
+ let l_gamma := mload(add(state, state_gamma))
+ let l_zeta := mload(add(state, state_zeta))
+ let l_alpha := mload(add(state, state_alpha))
+
+ let u := mulmod(calldataload(add(aproof, proof_grand_product_at_zeta_omega)), l_beta, r_mod)
+ let v := mulmod(l_beta, calldataload(add(aproof, proof_s1_at_zeta)), r_mod)
+ v := addmod(v, calldataload(add(aproof, proof_l_at_zeta)), r_mod)
+ v := addmod(v, l_gamma, r_mod)
+
+ let w := mulmod(l_beta, calldataload(add(aproof, proof_s2_at_zeta)), r_mod)
+ w := addmod(w, calldataload(add(aproof, proof_r_at_zeta)), r_mod)
+ w := addmod(w, l_gamma, r_mod)
+
+ let s1 := mulmod(u, v, r_mod)
+ s1 := mulmod(s1, w, r_mod)
+ s1 := mulmod(s1, l_alpha, r_mod)
+
+ let coset_square := mulmod(vk_coset_shift, vk_coset_shift, r_mod)
+ let betazeta := mulmod(l_beta, l_zeta, r_mod)
+ u := addmod(betazeta, calldataload(add(aproof, proof_l_at_zeta)), r_mod)
+ u := addmod(u, l_gamma, r_mod)
+
+ v := mulmod(betazeta, vk_coset_shift, r_mod)
+ v := addmod(v, calldataload(add(aproof, proof_r_at_zeta)), r_mod)
+ v := addmod(v, l_gamma, r_mod)
+
+ w := mulmod(betazeta, coset_square, r_mod)
+ w := addmod(w, calldataload(add(aproof, proof_o_at_zeta)), r_mod)
+ w := addmod(w, l_gamma, r_mod)
+
+ let s2 := mulmod(u, v, r_mod)
+ s2 := mulmod(s2, w, r_mod)
+ s2 := sub(r_mod, s2)
+ s2 := mulmod(s2, l_alpha, r_mod)
+ s2 := addmod(s2, mload(add(state, state_alpha_square_lagrange_0)), r_mod)
+
+ // at this stage:
+ // * sâ = Îą*Z(ÎŧÎļ)(l(Îļ)+β*sâ(Îļ)+Îŗ)*(r(Îļ)+β*sâ(Îļ)+Îŗ)*β
+ // * sâ = -Îą*(l(Îļ)+β*Îļ+Îŗ)*(r(Îļ)+β*u*Îļ+Îŗ)*(o(Îļ)+β*u²*Îļ+Îŗ) + ι²*Lâ(Îļ)
+
+ compute_commitment_linearised_polynomial_ec(aproof, s1, s2)
+ }
+
+ // compute Hâ + Îļáĩâē²*Hâ + Îļ²âŊáĩâē²âž*Hâ and store the result at
+ // state + state_folded_h
+ function fold_h(aproof) {
+ let state := mload(0x40)
+ let n_plus_two := add(vk_domain_size, 2)
+ let mPtr := add(mload(0x40), state_last_mem)
+ let zeta_power_n_plus_two := pow(mload(add(state, state_zeta)), n_plus_two, mPtr)
+ point_mul_calldata(add(state, state_folded_h_x), add(aproof, proof_h_2_x), zeta_power_n_plus_two, mPtr)
+ point_add_calldata(add(state, state_folded_h_x), add(state, state_folded_h_x), add(aproof, proof_h_1_x), mPtr)
+ point_mul(add(state, state_folded_h_x), add(state, state_folded_h_x), zeta_power_n_plus_two, mPtr)
+ point_add_calldata(add(state, state_folded_h_x), add(state, state_folded_h_x), add(aproof, proof_h_0_x), mPtr)
+ }
+
+ // check that
+ // L(Îļ)Qâ(Îļ)+r(Îļ)QáĩŖ(Îļ)+R(Îļ)L(Îļ)Qâ(Îļ)+O(Îļ)Qâ(Îļ)+Qâ(Îļ)+ÎŖáĩĸqc'áĩĸ(Îļ)BsbCommitmentáĩĸ(Îļ) +
+ // Îą*( Z(ÎŧÎļ)(l(Îļ)+β*sâ(Îļ)+Îŗ)*(r(Îļ)+β*sâ(Îļ)+Îŗ)*β*sâ(X)-Z(X)(l(Îļ)+β*id_1(Îļ)+Îŗ)*(r(Îļ)+β*id_2(Îļ)+Îŗ)*(o(Îļ)+β*id_3(Îļ)+Îŗ) ) )
+ // + ι²*Lâ(Îļ) =
+ // (Îļâŋ-1)H(Îļ)
+ function verify_quotient_poly_eval_at_zeta(aproof) {
+ let state := mload(0x40)
+
+ // (l(Îļ)+β*s1(Îļ)+Îŗ)
+ let s1 := add(mload(0x40), state_last_mem)
+ mstore(s1, mulmod(calldataload(add(aproof, proof_s1_at_zeta)), mload(add(state, state_beta)), r_mod))
+ mstore(s1, addmod(mload(s1), mload(add(state, state_gamma)), r_mod))
+ mstore(s1, addmod(mload(s1), calldataload(add(aproof, proof_l_at_zeta)), r_mod))
+
+ // (r(Îļ)+β*s2(Îļ)+Îŗ)
+ let s2 := add(s1, 0x20)
+ mstore(s2, mulmod(calldataload(add(aproof, proof_s2_at_zeta)), mload(add(state, state_beta)), r_mod))
+ mstore(s2, addmod(mload(s2), mload(add(state, state_gamma)), r_mod))
+ mstore(s2, addmod(mload(s2), calldataload(add(aproof, proof_r_at_zeta)), r_mod))
+ // _s2 := mload(s2)
+
+ // (o(Îļ)+Îŗ)
+ let o := add(s1, 0x40)
+ mstore(o, addmod(calldataload(add(aproof, proof_o_at_zeta)), mload(add(state, state_gamma)), r_mod))
+
+ // Îą*(Z(ÎŧÎļ))*(l(Îļ)+β*s1(Îļ)+Îŗ)*(r(Îļ)+β*s2(Îļ)+Îŗ)*(o(Îļ)+Îŗ)
+ mstore(s1, mulmod(mload(s1), mload(s2), r_mod))
+ mstore(s1, mulmod(mload(s1), mload(o), r_mod))
+ mstore(s1, mulmod(mload(s1), mload(add(state, state_alpha)), r_mod))
+ mstore(s1, mulmod(mload(s1), calldataload(add(aproof, proof_grand_product_at_zeta_omega)), r_mod))
+
+ let computed_quotient := add(s1, 0x60)
+
+ // linearizedpolynomial + pi(zeta)
+ mstore(
+ computed_quotient,
+ addmod(calldataload(add(aproof, proof_linearised_polynomial_at_zeta)), mload(add(state, state_pi)), r_mod)
+ )
+ mstore(computed_quotient, addmod(mload(computed_quotient), mload(s1), r_mod))
+ mstore(
+ computed_quotient,
+ addmod(mload(computed_quotient), sub(r_mod, mload(add(state, state_alpha_square_lagrange_0))), r_mod)
+ )
+ mstore(
+ s2,
+ mulmod(
+ calldataload(add(aproof, proof_quotient_polynomial_at_zeta)),
+ mload(add(state, state_zeta_power_n_minus_one)),
+ r_mod
+ )
+ )
+
+ mstore(add(state, state_success), eq(mload(computed_quotient), mload(s2)))
+ }
+
+ // BEGINNING utils math functions -------------------------------------------------
+ function point_add(dst, p, q, mPtr) {
+ let state := mload(0x40)
+ mstore(mPtr, mload(p))
+ mstore(add(mPtr, 0x20), mload(add(p, 0x20)))
+ mstore(add(mPtr, 0x40), mload(q))
+ mstore(add(mPtr, 0x60), mload(add(q, 0x20)))
+ let l_success := staticcall(gas(), 6, mPtr, 0x80, dst, 0x40)
+ if iszero(l_success) {
+ error_ec_op()
+ }
+ }
+
+ function point_add_calldata(dst, p, q, mPtr) {
+ let state := mload(0x40)
+ mstore(mPtr, mload(p))
+ mstore(add(mPtr, 0x20), mload(add(p, 0x20)))
+ mstore(add(mPtr, 0x40), calldataload(q))
+ mstore(add(mPtr, 0x60), calldataload(add(q, 0x20)))
+ let l_success := staticcall(gas(), 6, mPtr, 0x80, dst, 0x40)
+ if iszero(l_success) {
+ error_ec_op()
+ }
+ }
+
+ // dst <- [s]src
+ function point_mul(dst, src, s, mPtr) {
+ let state := mload(0x40)
+ mstore(mPtr, mload(src))
+ mstore(add(mPtr, 0x20), mload(add(src, 0x20)))
+ mstore(add(mPtr, 0x40), s)
+ let l_success := staticcall(gas(), 7, mPtr, 0x60, dst, 0x40)
+ if iszero(l_success) {
+ error_ec_op()
+ }
+ }
+
+ // dst <- [s]src
+ function point_mul_calldata(dst, src, s, mPtr) {
+ let state := mload(0x40)
+ mstore(mPtr, calldataload(src))
+ mstore(add(mPtr, 0x20), calldataload(add(src, 0x20)))
+ mstore(add(mPtr, 0x40), s)
+ let l_success := staticcall(gas(), 7, mPtr, 0x60, dst, 0x40)
+ if iszero(l_success) {
+ error_ec_op()
+ }
+ }
+
+ // dst <- dst + [s]src (Elliptic curve)
+ function point_acc_mul(dst, src, s, mPtr) {
+ let state := mload(0x40)
+ mstore(mPtr, mload(src))
+ mstore(add(mPtr, 0x20), mload(add(src, 0x20)))
+ mstore(add(mPtr, 0x40), s)
+ let l_success := staticcall(gas(), 7, mPtr, 0x60, mPtr, 0x40)
+ mstore(add(mPtr, 0x40), mload(dst))
+ mstore(add(mPtr, 0x60), mload(add(dst, 0x20)))
+ l_success := and(l_success, staticcall(gas(), 6, mPtr, 0x80, dst, 0x40))
+ if iszero(l_success) {
+ error_ec_op()
+ }
+ }
+
+ // dst <- dst + [s]src (Elliptic curve)
+ function point_acc_mul_calldata(dst, src, s, mPtr) {
+ let state := mload(0x40)
+ mstore(mPtr, calldataload(src))
+ mstore(add(mPtr, 0x20), calldataload(add(src, 0x20)))
+ mstore(add(mPtr, 0x40), s)
+ let l_success := staticcall(gas(), 7, mPtr, 0x60, mPtr, 0x40)
+ mstore(add(mPtr, 0x40), mload(dst))
+ mstore(add(mPtr, 0x60), mload(add(dst, 0x20)))
+ l_success := and(l_success, staticcall(gas(), 6, mPtr, 0x80, dst, 0x40))
+ if iszero(l_success) {
+ error_ec_op()
+ }
+ }
+
+ // dst <- dst + src (Fr) dst,src are addresses, s is a value
+ function fr_acc_mul_calldata(dst, src, s) {
+ let tmp := mulmod(calldataload(src), s, r_mod)
+ mstore(dst, addmod(mload(dst), tmp, r_mod))
+ }
+
+ // dst <- x ** e mod r (x, e are values, not pointers)
+ function pow(x, e, mPtr) -> res {
+ mstore(mPtr, 0x20)
+ mstore(add(mPtr, 0x20), 0x20)
+ mstore(add(mPtr, 0x40), 0x20)
+ mstore(add(mPtr, 0x60), x)
+ mstore(add(mPtr, 0x80), e)
+ mstore(add(mPtr, 0xa0), r_mod)
+ let check_staticcall := staticcall(gas(), 0x05, mPtr, 0xc0, mPtr, 0x20)
+ if eq(check_staticcall, 0) {
+ error_verify()
+ }
+ res := mload(mPtr)
+ }
+ }
+ }
+}
diff --git a/contracts/contracts/verifiers/Utils.sol b/contracts/contracts/verifiers/Utils.sol
new file mode 100644
index 000000000..e858b629d
--- /dev/null
+++ b/contracts/contracts/verifiers/Utils.sol
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: Apache-2.0
+
+// Copyright 2023 Consensys Software Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Code generated by gnark DO NOT EDIT
+
+pragma solidity 0.8.19;
+
+library Utils {
+ uint256 private constant r_mod = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
+ uint256 private constant bb = 340282366920938463463374607431768211456; // 2**128
+ uint256 private constant error_string_id = 0x08c379a000000000000000000000000000000000000000000000000000000000; // selector for function Error(string)
+ uint256 private constant zero_uint256 = 0;
+
+ uint8 private constant lenInBytes = 48;
+ uint8 private constant sizeDomain = 11;
+ uint8 private constant one = 1;
+ uint8 private constant two = 2;
+
+ /**
+ * @dev xmsg expands msg to a slice of lenInBytes bytes.
+ * https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-06#section-5
+ * https://tools.ietf.org/html/rfc8017#section-4.1 (I2OSP/O2ISP)
+ * @dev cf https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-06#section-5.2
+ * corresponds to https://github.com/ConsenSys/gnark-crypto/blob/develop/ecc/bn254/fr/element.go
+ */
+ function hash_fr(uint256 x, uint256 y) internal view returns (uint256 res) {
+ assembly {
+ function error_sha2_256() {
+ let ptError := mload(0x40)
+ mstore(ptError, error_string_id) // selector for function Error(string)
+ mstore(add(ptError, 0x4), 0x20)
+ mstore(add(ptError, 0x24), 0x19)
+ mstore(add(ptError, 0x44), "error staticcall sha2-256")
+ revert(ptError, 0x64)
+ }
+
+ // [0x00, .. , 0x00 || x, y, || 0, 48, 0, dst, sizeDomain]
+ // <- 64 bytes -> <-64b -> <- 1 bytes each ->
+ let mPtr := mload(0x40)
+
+ // [0x00, .., 0x00] 64 bytes of zero
+ mstore(mPtr, zero_uint256)
+ mstore(add(mPtr, 0x20), zero_uint256)
+
+ // msg = x || y , both on 32 bytes
+ mstore(add(mPtr, 0x40), x)
+ mstore(add(mPtr, 0x60), y)
+
+ // 0 || 48 || 0 all on 1 byte
+ mstore8(add(mPtr, 0x80), 0)
+ mstore8(add(mPtr, 0x81), lenInBytes)
+ mstore8(add(mPtr, 0x82), 0)
+
+ // "BSB22-Plonk" = [42, 53, 42, 32, 32, 2d, 50, 6c, 6f, 6e, 6b,]
+ mstore8(add(mPtr, 0x83), 0x42)
+ mstore8(add(mPtr, 0x84), 0x53)
+ mstore8(add(mPtr, 0x85), 0x42)
+ mstore8(add(mPtr, 0x86), 0x32)
+ mstore8(add(mPtr, 0x87), 0x32)
+ mstore8(add(mPtr, 0x88), 0x2d)
+ mstore8(add(mPtr, 0x89), 0x50)
+ mstore8(add(mPtr, 0x8a), 0x6c)
+ mstore8(add(mPtr, 0x8b), 0x6f)
+ mstore8(add(mPtr, 0x8c), 0x6e)
+ mstore8(add(mPtr, 0x8d), 0x6b)
+
+ // size domain
+ mstore8(add(mPtr, 0x8e), sizeDomain)
+
+ let success := staticcall(gas(), 0x2, mPtr, 0x8f, mPtr, 0x20)
+ if iszero(success) {
+ error_sha2_256()
+ }
+
+ let b0 := mload(mPtr)
+
+ // [b0 || one || dst || sizeDomain]
+ // <-64bytes -> <- 1 byte each ->
+ mstore8(add(mPtr, 0x20), one) // 1
+
+ mstore8(add(mPtr, 0x21), 0x42) // dst
+ mstore8(add(mPtr, 0x22), 0x53)
+ mstore8(add(mPtr, 0x23), 0x42)
+ mstore8(add(mPtr, 0x24), 0x32)
+ mstore8(add(mPtr, 0x25), 0x32)
+ mstore8(add(mPtr, 0x26), 0x2d)
+ mstore8(add(mPtr, 0x27), 0x50)
+ mstore8(add(mPtr, 0x28), 0x6c)
+ mstore8(add(mPtr, 0x29), 0x6f)
+ mstore8(add(mPtr, 0x2a), 0x6e)
+ mstore8(add(mPtr, 0x2b), 0x6b)
+
+ mstore8(add(mPtr, 0x2c), sizeDomain) // size domain
+ success := staticcall(gas(), 0x2, mPtr, 0x2d, mPtr, 0x20)
+ if iszero(success) {
+ error_sha2_256()
+ }
+
+ // b1 is located at mPtr. We store b2 at add(mPtr, 0x20)
+
+ // [b0^b1 || two || dst || sizeDomain]
+ // <-64bytes -> <- 1 byte each ->
+ mstore(add(mPtr, 0x20), xor(mload(mPtr), b0))
+ mstore8(add(mPtr, 0x40), two)
+
+ mstore8(add(mPtr, 0x41), 0x42) // dst
+ mstore8(add(mPtr, 0x42), 0x53)
+ mstore8(add(mPtr, 0x43), 0x42)
+ mstore8(add(mPtr, 0x44), 0x32)
+ mstore8(add(mPtr, 0x45), 0x32)
+ mstore8(add(mPtr, 0x46), 0x2d)
+ mstore8(add(mPtr, 0x47), 0x50)
+ mstore8(add(mPtr, 0x48), 0x6c)
+ mstore8(add(mPtr, 0x49), 0x6f)
+ mstore8(add(mPtr, 0x4a), 0x6e)
+ mstore8(add(mPtr, 0x4b), 0x6b)
+
+ mstore8(add(mPtr, 0x4c), sizeDomain) // size domain
+
+ let offset := add(mPtr, 0x20)
+ success := staticcall(gas(), 0x2, offset, 0x2d, offset, 0x20)
+ if iszero(success) {
+ error_sha2_256()
+ }
+
+ // at this point we have mPtr = [ b1 || b2] where b1 is on 32byes and b2 in 16bytes.
+ // we interpret it as a big integer mod r in big endian (similar to regular decimal notation)
+ // the result is then 2**(8*16)*mPtr[:32] + mPtr[32:48]
+ res := mulmod(mload(mPtr), bb, r_mod) // <- res = 2**128 * mPtr[:32]
+ offset := add(mPtr, 0x10)
+ for {
+ let i := 0
+ } lt(i, 0x10) {
+ i := add(i, 1)
+ } {
+ // mPtr <- [xx, xx, .., | 0, 0, .. 0 || b2 ]
+ mstore8(offset, 0x00)
+ offset := add(offset, 0x1)
+ }
+ let b1 := mload(add(mPtr, 0x10)) // b1 <- [0, 0, .., 0 || b2[:16] ]
+ res := addmod(res, b1, r_mod)
+ }
+ }
+}
diff --git a/contracts/contracts/verifiers/test/TestPlonkVerifier.sol b/contracts/contracts/verifiers/test/TestPlonkVerifier.sol
new file mode 100644
index 000000000..dfbbcd67f
--- /dev/null
+++ b/contracts/contracts/verifiers/test/TestPlonkVerifier.sol
@@ -0,0 +1,161 @@
+// SPDX-License-Identifier: Apache-2.0
+
+// Copyright 2023 Consensys Software Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Code generated by gnark DO NOT EDIT
+
+pragma solidity ^0.8.0;
+
+import { PlonkVerifier } from "../PlonkVerifier.sol";
+
+contract TestPlonkVerifier is PlonkVerifier {
+ event PrintBool(bool a);
+
+ struct Proof {
+ uint256 proof_l_com_x;
+ uint256 proof_l_com_y;
+ uint256 proof_r_com_x;
+ uint256 proof_r_com_y;
+ uint256 proof_o_com_x;
+ uint256 proof_o_com_y;
+ // h = h_0 + x^{n+2}h_1 + x^{2(n+2)}h_2
+ uint256 proof_h_0_x;
+ uint256 proof_h_0_y;
+ uint256 proof_h_1_x;
+ uint256 proof_h_1_y;
+ uint256 proof_h_2_x;
+ uint256 proof_h_2_y;
+ // wire values at zeta
+ uint256 proof_l_at_zeta;
+ uint256 proof_r_at_zeta;
+ uint256 proof_o_at_zeta;
+ //uint256[STATE_WIDTH-1] permutation_polynomials_at_zeta; // SĪ1(zeta),SĪ2(zeta)
+ uint256 proof_s1_at_zeta; // SĪ1(zeta)
+ uint256 proof_s2_at_zeta; // SĪ2(zeta)
+ //Bn254.G1Point grand_product_commitment; // [z(x)]
+ uint256 proof_grand_product_commitment_x;
+ uint256 proof_grand_product_commitment_y;
+ uint256 proof_grand_product_at_zeta_omega; // z(w*zeta)
+ uint256 proof_quotient_polynomial_at_zeta; // t(zeta)
+ uint256 proof_linearised_polynomial_at_zeta; // r(zeta)
+ // Folded proof for the opening of H, linearised poly, l, r, o, s_1, s_2, qcp
+ uint256 proof_batch_opening_at_zeta_x; // [Wzeta]
+ uint256 proof_batch_opening_at_zeta_y;
+ //Bn254.G1Point opening_at_zeta_omega_proof; // [Wzeta*omega]
+ uint256 proof_opening_at_zeta_omega_x;
+ uint256 proof_opening_at_zeta_omega_y;
+ uint256 proof_openings_selector_commit_api_at_zeta;
+ uint256 proof_selector_commit_api_commitment_x;
+ uint256 proof_selector_commit_api_commitment_y;
+ }
+
+ function get_proof() internal pure returns (bytes memory) {
+ Proof memory proof;
+
+ proof.proof_l_com_x = 3744411416677718418718249025975953553145844826632859814634603339807462553534;
+ proof.proof_l_com_y = 2002483772756815957015912903441086140018999198892778526965244942134289639218;
+ proof.proof_r_com_x = 17170271664910688865526094491499595184555794616133798609912071314418935895341;
+ proof.proof_r_com_y = 16301520447309505673124128500991625053582703955956763611801516691949669665097;
+ proof.proof_o_com_x = 4825576808590948965745513373961470704512799068446842376925273372384859414068;
+ proof.proof_o_com_y = 4531840120427723968486602059109253239818532868980197947154373010619305223741;
+ proof.proof_h_0_x = 16448615629060650443333517534393909724140628427800546855176467871089319528054;
+ proof.proof_h_0_y = 9593666135206906374733810777363128642334511724596460467905940294498468792378;
+ proof.proof_h_1_x = 11415941652140761914282242433054360734936936293084093086854599375713101439762;
+ proof.proof_h_1_y = 19518134108913023723777177398661000835575708574453231866849755881442619895919;
+ proof.proof_h_2_x = 16204844040413909819168697281594818140475755122355609600008008089839367039280;
+ proof.proof_h_2_y = 15234241442081831970720660386386534247775787714615387009463307053386950812729;
+ proof.proof_l_at_zeta = 17074062965358468862567917554077364884334843003439220191244394665607443975505;
+ proof.proof_r_at_zeta = 14071102587015473963867935668467373717561664420582503096680409156005767583341;
+ proof.proof_o_at_zeta = 15238001937141946442140316905180047553455748679003546455752237274931953877532;
+ proof.proof_s1_at_zeta = 9539936182520276789829269116724645558200024830729065697579211935000207816460;
+ proof.proof_s2_at_zeta = 19178247636566472751145148883334506528769265832114114322608665581126347370030;
+ proof
+ .proof_grand_product_commitment_x = 4766615987668140859166794700866698091140783551339492934946009214320629464429;
+ proof
+ .proof_grand_product_commitment_y = 9864550198679392984353203711740624505934442917292901771923620592593537074946;
+ proof
+ .proof_grand_product_at_zeta_omega = 988145577735546356369565544481381108206418086593338046248955117098652749544;
+ proof
+ .proof_quotient_polynomial_at_zeta = 16381283550711263028525990236744128256985547581268026415629801560597703420228;
+ proof
+ .proof_linearised_polynomial_at_zeta = 5305895313715590904290554981195113970992498996488884951757224273192208163501;
+ proof.proof_batch_opening_at_zeta_x = 1450918485336591275177565905096666585788210904467762402607446830983679023557;
+ proof.proof_batch_opening_at_zeta_y = 17449559573556982607998905245810434471246589036650215511419722672713241839234;
+ proof.proof_opening_at_zeta_omega_x = 20884451992705879592969812469963545781308701902951186902866398634897103257980;
+ proof.proof_opening_at_zeta_omega_y = 14488625452574321888591272184257848003439682873204582674823785313500952215248;
+ proof
+ .proof_openings_selector_commit_api_at_zeta = 7646473769094356296017416399592970032698126469362068554089835313188105035500;
+ proof
+ .proof_selector_commit_api_commitment_x = 2239425368272826282061405534574070761363587835810670164381692126212870760989;
+ proof
+ .proof_selector_commit_api_commitment_y = 9761541598739196851597518160386258153103789740009906698109345875186292566273;
+
+ bytes memory res;
+ res = abi.encodePacked(
+ proof.proof_l_com_x,
+ proof.proof_l_com_y,
+ proof.proof_r_com_x,
+ proof.proof_r_com_y,
+ proof.proof_o_com_x,
+ proof.proof_o_com_y,
+ proof.proof_h_0_x,
+ proof.proof_h_0_y,
+ proof.proof_h_1_x,
+ proof.proof_h_1_y,
+ proof.proof_h_2_x,
+ proof.proof_h_2_y
+ );
+ res = abi.encodePacked(res, proof.proof_l_at_zeta, proof.proof_r_at_zeta, proof.proof_o_at_zeta);
+ res = abi.encodePacked(
+ res,
+ proof.proof_s1_at_zeta,
+ proof.proof_s2_at_zeta,
+ proof.proof_grand_product_commitment_x,
+ proof.proof_grand_product_commitment_y,
+ proof.proof_grand_product_at_zeta_omega,
+ proof.proof_quotient_polynomial_at_zeta,
+ proof.proof_linearised_polynomial_at_zeta
+ );
+ res = abi.encodePacked(
+ res,
+ proof.proof_batch_opening_at_zeta_x,
+ proof.proof_batch_opening_at_zeta_y,
+ proof.proof_opening_at_zeta_omega_x,
+ proof.proof_opening_at_zeta_omega_y,
+ proof.proof_openings_selector_commit_api_at_zeta,
+ proof.proof_selector_commit_api_commitment_x,
+ proof.proof_selector_commit_api_commitment_y
+ );
+
+ return res;
+ }
+
+ function test_verifier_go(bytes memory proof, uint256[] memory public_inputs) public view {
+ bool check_proof = this.Verify(proof, public_inputs);
+ require(check_proof, "verification failed!");
+ }
+
+ function test_verifier() public {
+ uint256[] memory pi = new uint256[](1);
+
+ pi[0] = 0;
+
+ bytes memory proof = get_proof();
+
+ bool check_proof = this.Verify(proof, pi);
+ emit PrintBool(check_proof);
+ require(check_proof, "verification failed!");
+ }
+}
diff --git a/contracts/contracts/verifiers/test/TestPlonkVerifierFull.sol b/contracts/contracts/verifiers/test/TestPlonkVerifierFull.sol
new file mode 100644
index 000000000..b31cca9b1
--- /dev/null
+++ b/contracts/contracts/verifiers/test/TestPlonkVerifierFull.sol
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: Apache-2.0
+
+// Copyright 2023 Consensys Software Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Code generated by gnark DO NOT EDIT
+
+pragma solidity ^0.8.0;
+
+import { PlonkVerifierFull } from "../PlonkVerifierFull.sol";
+
+contract TestPlonkVerifierFull is PlonkVerifierFull {
+ function testVerifier(bytes calldata proof, uint256[] calldata pi) public view {
+ require(PlonkVerifierFull.Verify(proof, pi), "verification failed!");
+ }
+}
diff --git a/contracts/contracts/verifiers/test/TestPlonkVerifierFullLarge.sol b/contracts/contracts/verifiers/test/TestPlonkVerifierFullLarge.sol
new file mode 100644
index 000000000..0f2df2954
--- /dev/null
+++ b/contracts/contracts/verifiers/test/TestPlonkVerifierFullLarge.sol
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: Apache-2.0
+
+// Copyright 2023 Consensys Software Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Code generated by gnark DO NOT EDIT
+
+pragma solidity ^0.8.0;
+
+import { PlonkVerifierFullLarge } from "../PlonkVerifierFullLarge.sol";
+
+contract TestPlonkVerifierFullLarge is PlonkVerifierFullLarge {
+ function testVerifier(bytes calldata proof, uint256[] calldata pi) public view {
+ require(PlonkVerifierFullLarge.Verify(proof, pi), "verification failed!");
+ }
+}
diff --git a/contracts/deployments.json b/contracts/deployments.json
new file mode 100644
index 000000000..dae65fbf1
--- /dev/null
+++ b/contracts/deployments.json
@@ -0,0 +1,14 @@
+{
+ "zkevm_dev": {
+ "BridgedToken": "0x90Ca839E598F14a892aE77bc46Ad8327f9a3E65C",
+ "TokenBridge": "0x16B41c0D2796f724B77EE059A60c3c27036673c8"
+ },
+ "hardhat": {
+ "l1TokenBeacon": "0x21dF544947ba3E8b3c32561399E88B52Dc8b2823",
+ "l2TokenBeacon": "0x2E2Ed0Cfd3AD2f1d34481277b3204d807Ca2F8c2"
+ },
+ "l2": {
+ "BridgedToken": "0x5AABddC53F8B4d0f7Ba856e9d8340325f6680493",
+ "TokenBridge": "0x3807833f55F7dfea3931dda85F6e8f2E7d6A6821"
+ }
+}
\ No newline at end of file
diff --git a/contracts/docs/linea-token-bridge.md b/contracts/docs/linea-token-bridge.md
new file mode 100644
index 000000000..b639b753a
--- /dev/null
+++ b/contracts/docs/linea-token-bridge.md
@@ -0,0 +1,175 @@
+# Linea Token Bridge
+
+## Documentation
+
+Token Bridge is a canonical brige between Ethereum and Linea networks.
+
+## Install
+
+### Packages
+
+To install packages, execute:
+
+```shell
+npm i
+```
+
+### Config
+
+To setup config, copy the `.env.template` to `.env`, for example:
+
+```shell
+cp .env.template .env
+```
+
+Edit `.env` and add your configuration values.
+
+| Var | Description | Default |
+| --------------------------- | --------------------------- | ------------------------------------------ |
+| L1_RESERVED_TOKEN_ADDRESSES | Reserved L1 token addresses | 0x07865c6E87B9F70255377e024ace6630C1Eaa37F |
+| L2_RESERVED_TOKEN_ADDRESSES | Reserved L2 token addresses | 0xf56dc6695cF1f5c364eDEbC7Dc7077ac9B586068 |
+| ETHERSCAN_API_KEY | Etherscan API key | |
+
+## Deploy
+
+### On a Local Testnet network with mocked messaging service
+
+In a first terminal, run:
+
+```shell
+npx hardhat node
+```
+
+In a second terminal, run:
+
+```shell
+npx hardhat run --network localhost scripts/test/tokenBridge/deployMock.ts
+```
+
+### On a Goerli Testnet network with mocked messaging service
+
+In a terminal, run:
+
+```shell
+npx hardhat run --network goerli scripts/test/tokenBridge/deployMock.ts
+```
+
+### On Goerli Testnet and Goerli Linea Testnet
+
+In a terminal, run:
+
+```shell
+make deploy-testnet-token-bridge
+```
+
+Or
+
+```shell
+npx hardhat run --network zkevm_dev scripts/tokenBridge/deploy-1.ts
+npx hardhat run --network l2 scripts/tokenBridge/deploy-1.ts
+npx hardhat run --network zkevm_dev scripts/tokenBridge/deploy-2.ts
+npx hardhat run --network l2 scripts/tokenBridge/deploy-2.ts
+```
+
+All addresses created will be stored in the deployments.json file at the root of this project
+
+## Development
+
+### Testing
+
+To run tests, execute:
+
+```shell
+npm run test
+```
+
+or
+
+```shell
+npx hardhat test
+```
+
+To run tests on only one file, execute:
+
+```shell
+npx hardhat test test/tokenBridge/TokenBridge.ts
+```
+
+### Test coverage
+
+This project uses the Hardhat plugin [solidity-coverage](https://github.com/sc-forks/solidity-coverage/blob/master/HARDHAT_README.md) to assess the overall coverage of the unit tests.
+To generate a boilerplate report, use the following command:
+
+```shell
+npm run coverage
+```
+
+or
+
+```shell
+npx hardhat coverage --solcoverjs ./.solcover.js
+```
+
+The report will be generated in the `coverage` folder at the root of the repository. To visualize it in your web browser, you can use the `coverage/index.html` file.
+Note: the second command line might not work if the folder `coverage` already exists. If you encounter an issue, please delete the whole `coverage` folder and let hardhat-coverage regenerate a new one.
+
+### Contract verification on Etherscan
+
+To verify the contract on Etherscan.
+
+```shell
+ npx hardhat verify --network NETWORK DEPLOYED_CONTRACT_ADDRESS "Constructor argument 1" "Constructor argument 2"
+```
+
+### Gas Estimation
+
+You can estimate the contracts gas costs.
+
+- On a terminal start a local node:
+
+```shell
+npx hardhat node
+```
+
+- On another terminal, execute the gas estimation script:
+
+```shell
+npx hardhat run --network localhost scripts/tokenBridge/gasEstimation/gasEstimation.ts
+```
+
+It should return gas estimation:
+
+```shell
+basic bridgeToken: 162080
+bridgeToken with permit: 243910
+bridgeToken after confirmDeploy: 126453
+bridgeToken with permit after confirmDeploy: 202909
+```
+
+## Formatting Commands
+
+### Lint Solidity
+
+```bash
+npm run lint:sol
+```
+
+### Lint TypeScript
+
+```bash
+npm run lint:ts
+```
+
+### Prettier
+
+Check format code:
+
+```bash
+npm run prettier:check
+```
+
+Format code:
+
+```bash
+npm run prettier
+```
diff --git a/contracts/gnosisZodiac.d.ts b/contracts/gnosisZodiac.d.ts
new file mode 100644
index 000000000..d9f9d06a3
--- /dev/null
+++ b/contracts/gnosisZodiac.d.ts
@@ -0,0 +1 @@
+declare module "@gnosis.pm/zodiac/dist/esm";
diff --git a/contracts/hardhat.config.ts b/contracts/hardhat.config.ts
new file mode 100644
index 000000000..0725b0e4c
--- /dev/null
+++ b/contracts/hardhat.config.ts
@@ -0,0 +1,138 @@
+import "@nomicfoundation/hardhat-toolbox";
+import "@openzeppelin/hardhat-upgrades";
+import * as dotenv from "dotenv";
+import "hardhat-tracer";
+import { HardhatUserConfig } from "hardhat/config";
+import { MAX_GAS_LIMIT, getBlockchainNode, getContractOwnerPrivateKey, getL2BlockchainNode } from "./common";
+
+dotenv.config();
+
+const BLOCKCHAIN_TIMEOUT = process.env.BLOCKCHAIN_TIMEOUT_MS ? parseInt(process.env.BLOCKCHAIN_TIMEOUT_MS) : 300000;
+const EMPTY_HASH = "0x0000000000000000000000000000000000000000000000000000000000000000"
+
+const blockchainNode = getBlockchainNode();
+const l2BlockchainNode = getL2BlockchainNode();
+
+const ownerPrivateKey = getContractOwnerPrivateKey(
+ "../node-data/test/keys/contract_owner.acc",
+ "/node-data/test/keys/contract_owner.acc",
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ process.env.PRIVATE_KEY!,
+);
+
+const accounts = [];
+
+if (ownerPrivateKey) {
+ accounts.push(ownerPrivateKey);
+}
+
+const config: HardhatUserConfig = {
+ paths: {
+ artifacts: "./build",
+ },
+ solidity: {
+ compilers: [
+ {
+ version: "0.8.19",
+ settings: {
+ optimizer: {
+ enabled: true,
+ runs: 100000,
+ },
+ },
+ },
+ {
+ version: "0.8.15",
+ settings: {
+ optimizer: {
+ enabled: true,
+ runs: 100000,
+ },
+ },
+ },
+ ],
+ },
+ networks: {
+ mainnet: {
+ accounts: [process.env.MAINNET_PRIVATE_KEY ?? EMPTY_HASH],
+ url: "https://mainnet.infura.io/v3/" + process.env.INFURA_API_KEY
+ },
+ linea_mainnet: {
+ accounts: [process.env.LINEA_MAINNET_PRIVATE_KEY ?? EMPTY_HASH],
+ url: "https://linea-mainnet.infura.io/v3/" + process.env.INFURA_API_KEY
+ },
+ goerli: {
+ accounts: [process.env.GOERLI_PRIVATE_KEY ?? EMPTY_HASH],
+ url: "https://goerli.infura.io/v3/" + process.env.INFURA_API_KEY
+ },
+ linea_goerli: {
+ accounts: [process.env.LINEA_GOERLI_PRIVATE_KEY ?? EMPTY_HASH],
+ url: "https://linea-goerli.infura.io/v3/" + process.env.INFURA_API_KEY
+ },
+ besu: {
+ url: blockchainNode,
+ accounts,
+ gasPrice: 0,
+ gas: MAX_GAS_LIMIT,
+ blockGasLimit: MAX_GAS_LIMIT,
+ timeout: BLOCKCHAIN_TIMEOUT,
+ },
+ ganache: {
+ url: "http://127.0.0.1:8545",
+ accounts,
+ gasPrice: 0,
+ gas: MAX_GAS_LIMIT,
+ blockGasLimit: MAX_GAS_LIMIT,
+ timeout: BLOCKCHAIN_TIMEOUT,
+ },
+ zkevm_dev: {
+ url: blockchainNode,
+ accounts,
+ timeout: BLOCKCHAIN_TIMEOUT,
+ },
+ ...(l2BlockchainNode
+ ? {
+ l2: {
+ url: l2BlockchainNode,
+ accounts,
+ allowUnlimitedContractSize: true,
+ },
+ }
+ : {}),
+ },
+ gasReporter: {
+ enabled: !!process.env.REPORT_GAS,
+ },
+ mocha: {
+ timeout: 20000,
+ },
+ etherscan: {
+ apiKey: {
+ mainnet: process.env.ETHERSCAN_API_KEY ?? "",
+ goerli: process.env.ETHERSCAN_API_KEY ?? "",
+ linea_goerli: process.env.LINEASCAN_API_KEY ?? "",
+ linea_mainnet: process.env.LINEASCAN_API_KEY ?? "",
+ // TODO Add for linea mainnet
+ },
+ customChains: [
+ {
+ network: "linea_goerli",
+ chainId: 59140,
+ urls: {
+ apiURL: "https://api-goerli.lineascan.build/api",
+ browserURL: "https://goerli.lineascan.build/",
+ },
+ },
+ {
+ network: "linea_mainnet",
+ chainId: 59144,
+ urls: {
+ apiURL: "https://api.lineascan.build/api",
+ browserURL: "https://lineascan.build/",
+ },
+ },
+ ],
+ },
+};
+
+export default config;
diff --git a/contracts/mirror_linea_contracts.sh b/contracts/mirror_linea_contracts.sh
new file mode 100755
index 000000000..1a46efc7a
--- /dev/null
+++ b/contracts/mirror_linea_contracts.sh
@@ -0,0 +1,23 @@
+#!/usr/bin/env bash
+set -Eeu
+
+if [ $# -lt 1 ]; then
+ echo "Description: copy all the relevant files under the contracts folder to the local linea-contracts repo root folder"
+ echo "Usage: $0 destRepoRootDir"
+ echo Example: $0 ../../linea-contracts
+ exit
+fi
+
+export destRepoRootDir=$1
+echo destRepoRootDir = $destRepoRootDir
+
+echo "Copying all files under contracts with exclude list to $destRepoRootDir ..."
+rsync -av --exclude-from='.exclude_copy_list' ./ $destRepoRootDir
+echo "Done!!!"
+
+echo "Renaming package.json and package-lock.json to "linea-contracts" in $destRepoRootDir ..."
+cd $destRepoRootDir
+node -e "let pkg=require('./package.json'); pkg.name='linea-contracts'; require('fs').writeFileSync('package.json', JSON.stringify(pkg, null, 2));"
+node -e "let pkg=require('./package-lock.json'); pkg.name='linea-contracts'; pkg.packages[''].name='linea-contracts'; require('fs').writeFileSync('package-lock.json', JSON.stringify(pkg, null, 2));"
+cd -
+echo "All Done!!!!"
diff --git a/contracts/package-lock.json b/contracts/package-lock.json
new file mode 100644
index 000000000..d7a4eac36
--- /dev/null
+++ b/contracts/package-lock.json
@@ -0,0 +1,11708 @@
+{
+ "name": "smart_contract",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "smart_contract",
+ "devDependencies": {
+ "@gnosis.pm/zodiac": "^3.3.2",
+ "@nomicfoundation/hardhat-toolbox": "^2.0.2",
+ "@openzeppelin/contracts": "^4.9.2",
+ "@openzeppelin/contracts-upgradeable": "^4.9.2",
+ "@openzeppelin/hardhat-upgrades": "^1.28.0",
+ "@types/diff": "^5.0.3",
+ "@types/npmlog": "^4.1.4",
+ "@types/yargs": "^17.0.24",
+ "@typescript-eslint/eslint-plugin": "^5.59.0",
+ "@typescript-eslint/parser": "^5.59.0",
+ "colors": "^1.4.0",
+ "dotenv": "^16.0.3",
+ "edit-json-file": "^1.7.0",
+ "eslint": "^8.38.0",
+ "eslint-config-prettier": "^8.8.0",
+ "hardhat": "^2.14.0",
+ "hardhat-tracer": "^2.2.2",
+ "npmlog": "^7.0.1",
+ "prettier": "^2.8.7",
+ "prettier-plugin-solidity": "^1.1.3",
+ "solhint": "^3.4.1",
+ "solhint-plugin-prettier": "^0.0.5",
+ "yargs": "^17.7.1"
+ },
+ "engines": {
+ "node": "18.12.1",
+ "npm": "9.6.1"
+ }
+ },
+ "node_modules/@aashutoshrathi/word-wrap": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz",
+ "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/@aws-crypto/sha256-js": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-1.2.2.tgz",
+ "integrity": "sha512-Nr1QJIbW/afYYGzYvrF70LtaHrIRtd4TNAglX8BvlfxJLZ45SAmueIKYl5tWoNBPzp65ymXGFK0Bb1vZUpuc9g==",
+ "dev": true,
+ "dependencies": {
+ "@aws-crypto/util": "^1.2.2",
+ "@aws-sdk/types": "^3.1.0",
+ "tslib": "^1.11.1"
+ }
+ },
+ "node_modules/@aws-crypto/util": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-1.2.2.tgz",
+ "integrity": "sha512-H8PjG5WJ4wz0UXAFXeJjWCW1vkvIJ3qUUD+rGRwJ2/hj+xT58Qle2MTql/2MGzkU+1JLAFuR6aJpLAjHwhmwwg==",
+ "dev": true,
+ "dependencies": {
+ "@aws-sdk/types": "^3.1.0",
+ "@aws-sdk/util-utf8-browser": "^3.0.0",
+ "tslib": "^1.11.1"
+ }
+ },
+ "node_modules/@aws-sdk/types": {
+ "version": "3.378.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.378.0.tgz",
+ "integrity": "sha512-qP0CvR/ItgktmN8YXpGQglzzR/6s0nrsQ4zIfx3HMwpsBTwuouYahcCtF1Vr82P4NFcoDA412EJahJ2pIqEd+w==",
+ "dev": true,
+ "dependencies": {
+ "@smithy/types": "^2.0.2",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/types/node_modules/tslib": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz",
+ "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==",
+ "dev": true
+ },
+ "node_modules/@aws-sdk/util-utf8-browser": {
+ "version": "3.259.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz",
+ "integrity": "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==",
+ "dev": true,
+ "dependencies": {
+ "tslib": "^2.3.1"
+ }
+ },
+ "node_modules/@aws-sdk/util-utf8-browser/node_modules/tslib": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz",
+ "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==",
+ "dev": true
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz",
+ "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/highlight": "^7.22.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz",
+ "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/highlight": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz",
+ "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.22.5",
+ "chalk": "^2.0.0",
+ "js-tokens": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@chainsafe/as-sha256": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/@chainsafe/as-sha256/-/as-sha256-0.3.1.tgz",
+ "integrity": "sha512-hldFFYuf49ed7DAakWVXSJODuq3pzJEguD8tQ7h+sGkM18vja+OFoJI9krnGmgzyuZC2ETX0NOIcCTy31v2Mtg==",
+ "dev": true
+ },
+ "node_modules/@chainsafe/persistent-merkle-tree": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.4.2.tgz",
+ "integrity": "sha512-lLO3ihKPngXLTus/L7WHKaw9PnNJWizlOF1H9NNzHP6Xvh82vzg9F2bzkXhYIFshMZ2gTCEz8tq6STe7r5NDfQ==",
+ "dev": true,
+ "dependencies": {
+ "@chainsafe/as-sha256": "^0.3.1"
+ }
+ },
+ "node_modules/@chainsafe/ssz": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/@chainsafe/ssz/-/ssz-0.9.4.tgz",
+ "integrity": "sha512-77Qtg2N1ayqs4Bg/wvnWfg5Bta7iy7IRh8XqXh7oNMeP2HBbBwx8m6yTpA8p0EHItWPEBkgZd5S5/LSlp3GXuQ==",
+ "dev": true,
+ "dependencies": {
+ "@chainsafe/as-sha256": "^0.3.1",
+ "@chainsafe/persistent-merkle-tree": "^0.4.2",
+ "case": "^1.6.3"
+ }
+ },
+ "node_modules/@cspotcode/source-map-support": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
+ "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@jridgewell/trace-mapping": "0.3.9"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
+ "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==",
+ "dev": true,
+ "dependencies": {
+ "eslint-visitor-keys": "^3.3.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.2.tgz",
+ "integrity": "sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==",
+ "dev": true,
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.0.tgz",
+ "integrity": "sha512-Lj7DECXqIVCqnqjjHMPna4vn6GJcMgul/wuS0je9OZ9gsL0zzDpKPVtcG1HaDVc+9y+qgXneTeUMbCqXJNpH1A==",
+ "dev": true,
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^9.6.0",
+ "globals": "^13.19.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "8.44.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.44.0.tgz",
+ "integrity": "sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@ethersproject/abi": {
+ "version": "5.7.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz",
+ "integrity": "sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "dependencies": {
+ "@ethersproject/address": "^5.7.0",
+ "@ethersproject/bignumber": "^5.7.0",
+ "@ethersproject/bytes": "^5.7.0",
+ "@ethersproject/constants": "^5.7.0",
+ "@ethersproject/hash": "^5.7.0",
+ "@ethersproject/keccak256": "^5.7.0",
+ "@ethersproject/logger": "^5.7.0",
+ "@ethersproject/properties": "^5.7.0",
+ "@ethersproject/strings": "^5.7.0"
+ }
+ },
+ "node_modules/@ethersproject/abstract-provider": {
+ "version": "5.7.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz",
+ "integrity": "sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "dependencies": {
+ "@ethersproject/bignumber": "^5.7.0",
+ "@ethersproject/bytes": "^5.7.0",
+ "@ethersproject/logger": "^5.7.0",
+ "@ethersproject/networks": "^5.7.0",
+ "@ethersproject/properties": "^5.7.0",
+ "@ethersproject/transactions": "^5.7.0",
+ "@ethersproject/web": "^5.7.0"
+ }
+ },
+ "node_modules/@ethersproject/abstract-signer": {
+ "version": "5.7.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz",
+ "integrity": "sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "dependencies": {
+ "@ethersproject/abstract-provider": "^5.7.0",
+ "@ethersproject/bignumber": "^5.7.0",
+ "@ethersproject/bytes": "^5.7.0",
+ "@ethersproject/logger": "^5.7.0",
+ "@ethersproject/properties": "^5.7.0"
+ }
+ },
+ "node_modules/@ethersproject/address": {
+ "version": "5.7.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.7.0.tgz",
+ "integrity": "sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "dependencies": {
+ "@ethersproject/bignumber": "^5.7.0",
+ "@ethersproject/bytes": "^5.7.0",
+ "@ethersproject/keccak256": "^5.7.0",
+ "@ethersproject/logger": "^5.7.0",
+ "@ethersproject/rlp": "^5.7.0"
+ }
+ },
+ "node_modules/@ethersproject/base64": {
+ "version": "5.7.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.7.0.tgz",
+ "integrity": "sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "dependencies": {
+ "@ethersproject/bytes": "^5.7.0"
+ }
+ },
+ "node_modules/@ethersproject/basex": {
+ "version": "5.7.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.7.0.tgz",
+ "integrity": "sha512-ywlh43GwZLv2Voc2gQVTKBoVQ1mti3d8HK5aMxsfu/nRDnMmNqaSJ3r3n85HBByT8OpoY96SXM1FogC533T4zw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "dependencies": {
+ "@ethersproject/bytes": "^5.7.0",
+ "@ethersproject/properties": "^5.7.0"
+ }
+ },
+ "node_modules/@ethersproject/bignumber": {
+ "version": "5.7.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.7.0.tgz",
+ "integrity": "sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "dependencies": {
+ "@ethersproject/bytes": "^5.7.0",
+ "@ethersproject/logger": "^5.7.0",
+ "bn.js": "^5.2.1"
+ }
+ },
+ "node_modules/@ethersproject/bytes": {
+ "version": "5.7.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.7.0.tgz",
+ "integrity": "sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "dependencies": {
+ "@ethersproject/logger": "^5.7.0"
+ }
+ },
+ "node_modules/@ethersproject/constants": {
+ "version": "5.7.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.7.0.tgz",
+ "integrity": "sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "dependencies": {
+ "@ethersproject/bignumber": "^5.7.0"
+ }
+ },
+ "node_modules/@ethersproject/contracts": {
+ "version": "5.7.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.7.0.tgz",
+ "integrity": "sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "dependencies": {
+ "@ethersproject/abi": "^5.7.0",
+ "@ethersproject/abstract-provider": "^5.7.0",
+ "@ethersproject/abstract-signer": "^5.7.0",
+ "@ethersproject/address": "^5.7.0",
+ "@ethersproject/bignumber": "^5.7.0",
+ "@ethersproject/bytes": "^5.7.0",
+ "@ethersproject/constants": "^5.7.0",
+ "@ethersproject/logger": "^5.7.0",
+ "@ethersproject/properties": "^5.7.0",
+ "@ethersproject/transactions": "^5.7.0"
+ }
+ },
+ "node_modules/@ethersproject/hash": {
+ "version": "5.7.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.7.0.tgz",
+ "integrity": "sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "dependencies": {
+ "@ethersproject/abstract-signer": "^5.7.0",
+ "@ethersproject/address": "^5.7.0",
+ "@ethersproject/base64": "^5.7.0",
+ "@ethersproject/bignumber": "^5.7.0",
+ "@ethersproject/bytes": "^5.7.0",
+ "@ethersproject/keccak256": "^5.7.0",
+ "@ethersproject/logger": "^5.7.0",
+ "@ethersproject/properties": "^5.7.0",
+ "@ethersproject/strings": "^5.7.0"
+ }
+ },
+ "node_modules/@ethersproject/hdnode": {
+ "version": "5.7.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.7.0.tgz",
+ "integrity": "sha512-OmyYo9EENBPPf4ERhR7oj6uAtUAhYGqOnIS+jE5pTXvdKBS99ikzq1E7Iv0ZQZ5V36Lqx1qZLeak0Ra16qpeOg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "dependencies": {
+ "@ethersproject/abstract-signer": "^5.7.0",
+ "@ethersproject/basex": "^5.7.0",
+ "@ethersproject/bignumber": "^5.7.0",
+ "@ethersproject/bytes": "^5.7.0",
+ "@ethersproject/logger": "^5.7.0",
+ "@ethersproject/pbkdf2": "^5.7.0",
+ "@ethersproject/properties": "^5.7.0",
+ "@ethersproject/sha2": "^5.7.0",
+ "@ethersproject/signing-key": "^5.7.0",
+ "@ethersproject/strings": "^5.7.0",
+ "@ethersproject/transactions": "^5.7.0",
+ "@ethersproject/wordlists": "^5.7.0"
+ }
+ },
+ "node_modules/@ethersproject/json-wallets": {
+ "version": "5.7.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.7.0.tgz",
+ "integrity": "sha512-8oee5Xgu6+RKgJTkvEMl2wDgSPSAQ9MB/3JYjFV9jlKvcYHUXZC+cQp0njgmxdHkYWn8s6/IqIZYm0YWCjO/0g==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "dependencies": {
+ "@ethersproject/abstract-signer": "^5.7.0",
+ "@ethersproject/address": "^5.7.0",
+ "@ethersproject/bytes": "^5.7.0",
+ "@ethersproject/hdnode": "^5.7.0",
+ "@ethersproject/keccak256": "^5.7.0",
+ "@ethersproject/logger": "^5.7.0",
+ "@ethersproject/pbkdf2": "^5.7.0",
+ "@ethersproject/properties": "^5.7.0",
+ "@ethersproject/random": "^5.7.0",
+ "@ethersproject/strings": "^5.7.0",
+ "@ethersproject/transactions": "^5.7.0",
+ "aes-js": "3.0.0",
+ "scrypt-js": "3.0.1"
+ }
+ },
+ "node_modules/@ethersproject/keccak256": {
+ "version": "5.7.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.7.0.tgz",
+ "integrity": "sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "dependencies": {
+ "@ethersproject/bytes": "^5.7.0",
+ "js-sha3": "0.8.0"
+ }
+ },
+ "node_modules/@ethersproject/logger": {
+ "version": "5.7.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.7.0.tgz",
+ "integrity": "sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ]
+ },
+ "node_modules/@ethersproject/networks": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.7.1.tgz",
+ "integrity": "sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "dependencies": {
+ "@ethersproject/logger": "^5.7.0"
+ }
+ },
+ "node_modules/@ethersproject/pbkdf2": {
+ "version": "5.7.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.7.0.tgz",
+ "integrity": "sha512-oR/dBRZR6GTyaofd86DehG72hY6NpAjhabkhxgr3X2FpJtJuodEl2auADWBZfhDHgVCbu3/H/Ocq2uC6dpNjjw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "dependencies": {
+ "@ethersproject/bytes": "^5.7.0",
+ "@ethersproject/sha2": "^5.7.0"
+ }
+ },
+ "node_modules/@ethersproject/properties": {
+ "version": "5.7.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.7.0.tgz",
+ "integrity": "sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "dependencies": {
+ "@ethersproject/logger": "^5.7.0"
+ }
+ },
+ "node_modules/@ethersproject/providers": {
+ "version": "5.7.2",
+ "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.7.2.tgz",
+ "integrity": "sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "dependencies": {
+ "@ethersproject/abstract-provider": "^5.7.0",
+ "@ethersproject/abstract-signer": "^5.7.0",
+ "@ethersproject/address": "^5.7.0",
+ "@ethersproject/base64": "^5.7.0",
+ "@ethersproject/basex": "^5.7.0",
+ "@ethersproject/bignumber": "^5.7.0",
+ "@ethersproject/bytes": "^5.7.0",
+ "@ethersproject/constants": "^5.7.0",
+ "@ethersproject/hash": "^5.7.0",
+ "@ethersproject/logger": "^5.7.0",
+ "@ethersproject/networks": "^5.7.0",
+ "@ethersproject/properties": "^5.7.0",
+ "@ethersproject/random": "^5.7.0",
+ "@ethersproject/rlp": "^5.7.0",
+ "@ethersproject/sha2": "^5.7.0",
+ "@ethersproject/strings": "^5.7.0",
+ "@ethersproject/transactions": "^5.7.0",
+ "@ethersproject/web": "^5.7.0",
+ "bech32": "1.1.4",
+ "ws": "7.4.6"
+ }
+ },
+ "node_modules/@ethersproject/random": {
+ "version": "5.7.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.7.0.tgz",
+ "integrity": "sha512-19WjScqRA8IIeWclFme75VMXSBvi4e6InrUNuaR4s5pTF2qNhcGdCUwdxUVGtDDqC00sDLCO93jPQoDUH4HVmQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "dependencies": {
+ "@ethersproject/bytes": "^5.7.0",
+ "@ethersproject/logger": "^5.7.0"
+ }
+ },
+ "node_modules/@ethersproject/rlp": {
+ "version": "5.7.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.7.0.tgz",
+ "integrity": "sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "dependencies": {
+ "@ethersproject/bytes": "^5.7.0",
+ "@ethersproject/logger": "^5.7.0"
+ }
+ },
+ "node_modules/@ethersproject/sha2": {
+ "version": "5.7.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.7.0.tgz",
+ "integrity": "sha512-gKlH42riwb3KYp0reLsFTokByAKoJdgFCwI+CCiX/k+Jm2mbNs6oOaCjYQSlI1+XBVejwH2KrmCbMAT/GnRDQw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "dependencies": {
+ "@ethersproject/bytes": "^5.7.0",
+ "@ethersproject/logger": "^5.7.0",
+ "hash.js": "1.1.7"
+ }
+ },
+ "node_modules/@ethersproject/signing-key": {
+ "version": "5.7.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.7.0.tgz",
+ "integrity": "sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "dependencies": {
+ "@ethersproject/bytes": "^5.7.0",
+ "@ethersproject/logger": "^5.7.0",
+ "@ethersproject/properties": "^5.7.0",
+ "bn.js": "^5.2.1",
+ "elliptic": "6.5.4",
+ "hash.js": "1.1.7"
+ }
+ },
+ "node_modules/@ethersproject/solidity": {
+ "version": "5.7.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.7.0.tgz",
+ "integrity": "sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "dependencies": {
+ "@ethersproject/bignumber": "^5.7.0",
+ "@ethersproject/bytes": "^5.7.0",
+ "@ethersproject/keccak256": "^5.7.0",
+ "@ethersproject/logger": "^5.7.0",
+ "@ethersproject/sha2": "^5.7.0",
+ "@ethersproject/strings": "^5.7.0"
+ }
+ },
+ "node_modules/@ethersproject/strings": {
+ "version": "5.7.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.7.0.tgz",
+ "integrity": "sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "dependencies": {
+ "@ethersproject/bytes": "^5.7.0",
+ "@ethersproject/constants": "^5.7.0",
+ "@ethersproject/logger": "^5.7.0"
+ }
+ },
+ "node_modules/@ethersproject/transactions": {
+ "version": "5.7.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.7.0.tgz",
+ "integrity": "sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "dependencies": {
+ "@ethersproject/address": "^5.7.0",
+ "@ethersproject/bignumber": "^5.7.0",
+ "@ethersproject/bytes": "^5.7.0",
+ "@ethersproject/constants": "^5.7.0",
+ "@ethersproject/keccak256": "^5.7.0",
+ "@ethersproject/logger": "^5.7.0",
+ "@ethersproject/properties": "^5.7.0",
+ "@ethersproject/rlp": "^5.7.0",
+ "@ethersproject/signing-key": "^5.7.0"
+ }
+ },
+ "node_modules/@ethersproject/units": {
+ "version": "5.7.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.7.0.tgz",
+ "integrity": "sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "dependencies": {
+ "@ethersproject/bignumber": "^5.7.0",
+ "@ethersproject/constants": "^5.7.0",
+ "@ethersproject/logger": "^5.7.0"
+ }
+ },
+ "node_modules/@ethersproject/wallet": {
+ "version": "5.7.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.7.0.tgz",
+ "integrity": "sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "dependencies": {
+ "@ethersproject/abstract-provider": "^5.7.0",
+ "@ethersproject/abstract-signer": "^5.7.0",
+ "@ethersproject/address": "^5.7.0",
+ "@ethersproject/bignumber": "^5.7.0",
+ "@ethersproject/bytes": "^5.7.0",
+ "@ethersproject/hash": "^5.7.0",
+ "@ethersproject/hdnode": "^5.7.0",
+ "@ethersproject/json-wallets": "^5.7.0",
+ "@ethersproject/keccak256": "^5.7.0",
+ "@ethersproject/logger": "^5.7.0",
+ "@ethersproject/properties": "^5.7.0",
+ "@ethersproject/random": "^5.7.0",
+ "@ethersproject/signing-key": "^5.7.0",
+ "@ethersproject/transactions": "^5.7.0",
+ "@ethersproject/wordlists": "^5.7.0"
+ }
+ },
+ "node_modules/@ethersproject/web": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.7.1.tgz",
+ "integrity": "sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "dependencies": {
+ "@ethersproject/base64": "^5.7.0",
+ "@ethersproject/bytes": "^5.7.0",
+ "@ethersproject/logger": "^5.7.0",
+ "@ethersproject/properties": "^5.7.0",
+ "@ethersproject/strings": "^5.7.0"
+ }
+ },
+ "node_modules/@ethersproject/wordlists": {
+ "version": "5.7.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.7.0.tgz",
+ "integrity": "sha512-S2TFNJNfHWVHNE6cNDjbVlZ6MgE17MIxMbMg2zv3wn+3XSJGosL1m9ZVv3GXCf/2ymSsQ+hRI5IzoMJTG6aoVA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "dependencies": {
+ "@ethersproject/bytes": "^5.7.0",
+ "@ethersproject/hash": "^5.7.0",
+ "@ethersproject/logger": "^5.7.0",
+ "@ethersproject/properties": "^5.7.0",
+ "@ethersproject/strings": "^5.7.0"
+ }
+ },
+ "node_modules/@gnosis.pm/mock-contract": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@gnosis.pm/mock-contract/-/mock-contract-4.0.0.tgz",
+ "integrity": "sha512-SkRq2KwPx6vo0LAjSc8JhgQstrQFXRyn2yqquIfub7r2WHi5nUbF8beeSSXsd36hvBcQxQfmOIYNYRpj9JOhrQ==",
+ "dev": true
+ },
+ "node_modules/@gnosis.pm/safe-contracts": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@gnosis.pm/safe-contracts/-/safe-contracts-1.3.0.tgz",
+ "integrity": "sha512-1p+1HwGvxGUVzVkFjNzglwHrLNA67U/axP0Ct85FzzH8yhGJb4t9jDjPYocVMzLorDoWAfKicGy1akPY9jXRVw==",
+ "dev": true,
+ "peerDependencies": {
+ "ethers": "^5.1.4"
+ }
+ },
+ "node_modules/@gnosis.pm/zodiac": {
+ "version": "3.3.4",
+ "resolved": "https://registry.npmjs.org/@gnosis.pm/zodiac/-/zodiac-3.3.4.tgz",
+ "integrity": "sha512-z/AWSVpn4U65XkHcDr5qvlnNjk0Kxvq6Wba8RZ28xL6kzljUV8UYKc1lAebnaLW6vOIINkJFfxJiNhz6Jti0ww==",
+ "dev": true,
+ "dependencies": {
+ "@gnosis.pm/mock-contract": "^4.0.0",
+ "@gnosis.pm/safe-contracts": "1.3.0",
+ "@openzeppelin/contracts": "^4.8.1",
+ "@openzeppelin/contracts-upgradeable": "^4.8.1",
+ "ethers": "^5.7.1"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array": {
+ "version": "0.11.10",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz",
+ "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==",
+ "dev": true,
+ "dependencies": {
+ "@humanwhocodes/object-schema": "^1.2.1",
+ "debug": "^4.1.1",
+ "minimatch": "^3.0.5"
+ },
+ "engines": {
+ "node": ">=10.10.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/object-schema": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
+ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
+ "dev": true
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
+ "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.4.15",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
+ "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
+ "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.0.3",
+ "@jridgewell/sourcemap-codec": "^1.4.10"
+ }
+ },
+ "node_modules/@metamask/eth-sig-util": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@metamask/eth-sig-util/-/eth-sig-util-4.0.1.tgz",
+ "integrity": "sha512-tghyZKLHZjcdlDqCA3gNZmLeR0XvOE9U1qoQO9ohyAZT6Pya+H9vkBPcsyXytmYLNgVoin7CKCmweo/R43V+tQ==",
+ "dev": true,
+ "dependencies": {
+ "ethereumjs-abi": "^0.6.8",
+ "ethereumjs-util": "^6.2.1",
+ "ethjs-util": "^0.1.6",
+ "tweetnacl": "^1.0.3",
+ "tweetnacl-util": "^0.15.1"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/@metamask/eth-sig-util/node_modules/@types/bn.js": {
+ "version": "4.11.6",
+ "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz",
+ "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@metamask/eth-sig-util/node_modules/bn.js": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
+ "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
+ "dev": true
+ },
+ "node_modules/@metamask/eth-sig-util/node_modules/ethereumjs-util": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz",
+ "integrity": "sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==",
+ "dev": true,
+ "dependencies": {
+ "@types/bn.js": "^4.11.3",
+ "bn.js": "^4.11.0",
+ "create-hash": "^1.1.2",
+ "elliptic": "^6.5.2",
+ "ethereum-cryptography": "^0.1.3",
+ "ethjs-util": "0.1.6",
+ "rlp": "^2.2.3"
+ }
+ },
+ "node_modules/@noble/hashes": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.2.0.tgz",
+ "integrity": "sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ ]
+ },
+ "node_modules/@noble/secp256k1": {
+ "version": "1.7.1",
+ "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.1.tgz",
+ "integrity": "sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ ]
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nomicfoundation/ethereumjs-block": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-block/-/ethereumjs-block-5.0.1.tgz",
+ "integrity": "sha512-u1Yioemi6Ckj3xspygu/SfFvm8vZEO8/Yx5a1QLzi6nVU0jz3Pg2OmHKJ5w+D9Ogk1vhwRiqEBAqcb0GVhCyHw==",
+ "dev": true,
+ "dependencies": {
+ "@nomicfoundation/ethereumjs-common": "4.0.1",
+ "@nomicfoundation/ethereumjs-rlp": "5.0.1",
+ "@nomicfoundation/ethereumjs-trie": "6.0.1",
+ "@nomicfoundation/ethereumjs-tx": "5.0.1",
+ "@nomicfoundation/ethereumjs-util": "9.0.1",
+ "ethereum-cryptography": "0.1.3",
+ "ethers": "^5.7.1"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@nomicfoundation/ethereumjs-blockchain": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-blockchain/-/ethereumjs-blockchain-7.0.1.tgz",
+ "integrity": "sha512-NhzndlGg829XXbqJEYrF1VeZhAwSPgsK/OB7TVrdzft3y918hW5KNd7gIZ85sn6peDZOdjBsAXIpXZ38oBYE5A==",
+ "dev": true,
+ "dependencies": {
+ "@nomicfoundation/ethereumjs-block": "5.0.1",
+ "@nomicfoundation/ethereumjs-common": "4.0.1",
+ "@nomicfoundation/ethereumjs-ethash": "3.0.1",
+ "@nomicfoundation/ethereumjs-rlp": "5.0.1",
+ "@nomicfoundation/ethereumjs-trie": "6.0.1",
+ "@nomicfoundation/ethereumjs-tx": "5.0.1",
+ "@nomicfoundation/ethereumjs-util": "9.0.1",
+ "abstract-level": "^1.0.3",
+ "debug": "^4.3.3",
+ "ethereum-cryptography": "0.1.3",
+ "level": "^8.0.0",
+ "lru-cache": "^5.1.1",
+ "memory-level": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@nomicfoundation/ethereumjs-common": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-4.0.1.tgz",
+ "integrity": "sha512-OBErlkfp54GpeiE06brBW/TTbtbuBJV5YI5Nz/aB2evTDo+KawyEzPjBlSr84z/8MFfj8wS2wxzQX1o32cev5g==",
+ "dev": true,
+ "dependencies": {
+ "@nomicfoundation/ethereumjs-util": "9.0.1",
+ "crc-32": "^1.2.0"
+ }
+ },
+ "node_modules/@nomicfoundation/ethereumjs-ethash": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-ethash/-/ethereumjs-ethash-3.0.1.tgz",
+ "integrity": "sha512-KDjGIB5igzWOp8Ik5I6QiRH5DH+XgILlplsHR7TEuWANZA759G6krQ6o8bvj+tRUz08YygMQu/sGd9mJ1DYT8w==",
+ "dev": true,
+ "dependencies": {
+ "@nomicfoundation/ethereumjs-block": "5.0.1",
+ "@nomicfoundation/ethereumjs-rlp": "5.0.1",
+ "@nomicfoundation/ethereumjs-util": "9.0.1",
+ "abstract-level": "^1.0.3",
+ "bigint-crypto-utils": "^3.0.23",
+ "ethereum-cryptography": "0.1.3"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@nomicfoundation/ethereumjs-evm": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-evm/-/ethereumjs-evm-2.0.1.tgz",
+ "integrity": "sha512-oL8vJcnk0Bx/onl+TgQOQ1t/534GKFaEG17fZmwtPFeH8S5soiBYPCLUrvANOl4sCp9elYxIMzIiTtMtNNN8EQ==",
+ "dev": true,
+ "dependencies": {
+ "@ethersproject/providers": "^5.7.1",
+ "@nomicfoundation/ethereumjs-common": "4.0.1",
+ "@nomicfoundation/ethereumjs-tx": "5.0.1",
+ "@nomicfoundation/ethereumjs-util": "9.0.1",
+ "debug": "^4.3.3",
+ "ethereum-cryptography": "0.1.3",
+ "mcl-wasm": "^0.7.1",
+ "rustbn.js": "~0.2.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@nomicfoundation/ethereumjs-rlp": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-5.0.1.tgz",
+ "integrity": "sha512-xtxrMGa8kP4zF5ApBQBtjlSbN5E2HI8m8FYgVSYAnO6ssUoY5pVPGy2H8+xdf/bmMa22Ce8nWMH3aEW8CcqMeQ==",
+ "dev": true,
+ "bin": {
+ "rlp": "bin/rlp"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@nomicfoundation/ethereumjs-statemanager": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-statemanager/-/ethereumjs-statemanager-2.0.1.tgz",
+ "integrity": "sha512-B5ApMOnlruVOR7gisBaYwFX+L/AP7i/2oAahatssjPIBVDF6wTX1K7Qpa39E/nzsH8iYuL3krkYeUFIdO3EMUQ==",
+ "dev": true,
+ "dependencies": {
+ "@nomicfoundation/ethereumjs-common": "4.0.1",
+ "@nomicfoundation/ethereumjs-rlp": "5.0.1",
+ "debug": "^4.3.3",
+ "ethereum-cryptography": "0.1.3",
+ "ethers": "^5.7.1",
+ "js-sdsl": "^4.1.4"
+ }
+ },
+ "node_modules/@nomicfoundation/ethereumjs-trie": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-trie/-/ethereumjs-trie-6.0.1.tgz",
+ "integrity": "sha512-A64It/IMpDVODzCgxDgAAla8jNjNtsoQZIzZUfIV5AY6Coi4nvn7+VReBn5itlxMiL2yaTlQr9TRWp3CSI6VoA==",
+ "dev": true,
+ "dependencies": {
+ "@nomicfoundation/ethereumjs-rlp": "5.0.1",
+ "@nomicfoundation/ethereumjs-util": "9.0.1",
+ "@types/readable-stream": "^2.3.13",
+ "ethereum-cryptography": "0.1.3",
+ "readable-stream": "^3.6.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@nomicfoundation/ethereumjs-tx": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-5.0.1.tgz",
+ "integrity": "sha512-0HwxUF2u2hrsIM1fsasjXvlbDOq1ZHFV2dd1yGq8CA+MEYhaxZr8OTScpVkkxqMwBcc5y83FyPl0J9MZn3kY0w==",
+ "dev": true,
+ "dependencies": {
+ "@chainsafe/ssz": "^0.9.2",
+ "@ethersproject/providers": "^5.7.2",
+ "@nomicfoundation/ethereumjs-common": "4.0.1",
+ "@nomicfoundation/ethereumjs-rlp": "5.0.1",
+ "@nomicfoundation/ethereumjs-util": "9.0.1",
+ "ethereum-cryptography": "0.1.3"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@nomicfoundation/ethereumjs-util": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-9.0.1.tgz",
+ "integrity": "sha512-TwbhOWQ8QoSCFhV/DDfSmyfFIHjPjFBj957219+V3jTZYZ2rf9PmDtNOeZWAE3p3vlp8xb02XGpd0v6nTUPbsA==",
+ "dev": true,
+ "dependencies": {
+ "@chainsafe/ssz": "^0.10.0",
+ "@nomicfoundation/ethereumjs-rlp": "5.0.1",
+ "ethereum-cryptography": "0.1.3"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@nomicfoundation/ethereumjs-util/node_modules/@chainsafe/persistent-merkle-tree": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.5.0.tgz",
+ "integrity": "sha512-l0V1b5clxA3iwQLXP40zYjyZYospQLZXzBVIhhr9kDg/1qHZfzzHw0jj4VPBijfYCArZDlPkRi1wZaV2POKeuw==",
+ "dev": true,
+ "dependencies": {
+ "@chainsafe/as-sha256": "^0.3.1"
+ }
+ },
+ "node_modules/@nomicfoundation/ethereumjs-util/node_modules/@chainsafe/ssz": {
+ "version": "0.10.2",
+ "resolved": "https://registry.npmjs.org/@chainsafe/ssz/-/ssz-0.10.2.tgz",
+ "integrity": "sha512-/NL3Lh8K+0q7A3LsiFq09YXS9fPE+ead2rr7vM2QK8PLzrNsw3uqrif9bpRX5UxgeRjM+vYi+boCM3+GM4ovXg==",
+ "dev": true,
+ "dependencies": {
+ "@chainsafe/as-sha256": "^0.3.1",
+ "@chainsafe/persistent-merkle-tree": "^0.5.0"
+ }
+ },
+ "node_modules/@nomicfoundation/ethereumjs-vm": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-vm/-/ethereumjs-vm-7.0.1.tgz",
+ "integrity": "sha512-rArhyn0jPsS/D+ApFsz3yVJMQ29+pVzNZ0VJgkzAZ+7FqXSRtThl1C1prhmlVr3YNUlfpZ69Ak+RUT4g7VoOuQ==",
+ "dev": true,
+ "dependencies": {
+ "@nomicfoundation/ethereumjs-block": "5.0.1",
+ "@nomicfoundation/ethereumjs-blockchain": "7.0.1",
+ "@nomicfoundation/ethereumjs-common": "4.0.1",
+ "@nomicfoundation/ethereumjs-evm": "2.0.1",
+ "@nomicfoundation/ethereumjs-rlp": "5.0.1",
+ "@nomicfoundation/ethereumjs-statemanager": "2.0.1",
+ "@nomicfoundation/ethereumjs-trie": "6.0.1",
+ "@nomicfoundation/ethereumjs-tx": "5.0.1",
+ "@nomicfoundation/ethereumjs-util": "9.0.1",
+ "debug": "^4.3.3",
+ "ethereum-cryptography": "0.1.3",
+ "mcl-wasm": "^0.7.1",
+ "rustbn.js": "~0.2.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@nomicfoundation/hardhat-chai-matchers": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-chai-matchers/-/hardhat-chai-matchers-1.0.6.tgz",
+ "integrity": "sha512-f5ZMNmabZeZegEfuxn/0kW+mm7+yV7VNDxLpMOMGXWFJ2l/Ct3QShujzDRF9cOkK9Ui/hbDeOWGZqyQALDXVCQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@ethersproject/abi": "^5.1.2",
+ "@types/chai-as-promised": "^7.1.3",
+ "chai-as-promised": "^7.1.1",
+ "deep-eql": "^4.0.1",
+ "ordinal": "^1.0.3"
+ },
+ "peerDependencies": {
+ "@nomiclabs/hardhat-ethers": "^2.0.0",
+ "chai": "^4.2.0",
+ "ethers": "^5.0.0",
+ "hardhat": "^2.9.4"
+ }
+ },
+ "node_modules/@nomicfoundation/hardhat-network-helpers": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-network-helpers/-/hardhat-network-helpers-1.0.8.tgz",
+ "integrity": "sha512-MNqQbzUJZnCMIYvlniC3U+kcavz/PhhQSsY90tbEtUyMj/IQqsLwIRZa4ctjABh3Bz0KCh9OXUZ7Yk/d9hr45Q==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "ethereumjs-util": "^7.1.4"
+ },
+ "peerDependencies": {
+ "hardhat": "^2.9.5"
+ }
+ },
+ "node_modules/@nomicfoundation/hardhat-toolbox": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-toolbox/-/hardhat-toolbox-2.0.2.tgz",
+ "integrity": "sha512-vnN1AzxbvpSx9pfdRHbUzTRIXpMLPXnUlkW855VaDk6N1pwRaQ2gNzEmFAABk4lWf11E00PKwFd/q27HuwYrYg==",
+ "dev": true,
+ "peerDependencies": {
+ "@ethersproject/abi": "^5.4.7",
+ "@ethersproject/providers": "^5.4.7",
+ "@nomicfoundation/hardhat-chai-matchers": "^1.0.0",
+ "@nomicfoundation/hardhat-network-helpers": "^1.0.0",
+ "@nomiclabs/hardhat-ethers": "^2.0.0",
+ "@nomiclabs/hardhat-etherscan": "^3.0.0",
+ "@typechain/ethers-v5": "^10.1.0",
+ "@typechain/hardhat": "^6.1.2",
+ "@types/chai": "^4.2.0",
+ "@types/mocha": ">=9.1.0",
+ "@types/node": ">=12.0.0",
+ "chai": "^4.2.0",
+ "ethers": "^5.4.7",
+ "hardhat": "^2.11.0",
+ "hardhat-gas-reporter": "^1.0.8",
+ "solidity-coverage": "^0.8.1",
+ "ts-node": ">=8.0.0",
+ "typechain": "^8.1.0",
+ "typescript": ">=4.5.0"
+ }
+ },
+ "node_modules/@nomicfoundation/solidity-analyzer": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer/-/solidity-analyzer-0.1.1.tgz",
+ "integrity": "sha512-1LMtXj1puAxyFusBgUIy5pZk3073cNXYnXUpuNKFghHbIit/xZgbk0AokpUADbNm3gyD6bFWl3LRFh3dhVdREg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 12"
+ },
+ "optionalDependencies": {
+ "@nomicfoundation/solidity-analyzer-darwin-arm64": "0.1.1",
+ "@nomicfoundation/solidity-analyzer-darwin-x64": "0.1.1",
+ "@nomicfoundation/solidity-analyzer-freebsd-x64": "0.1.1",
+ "@nomicfoundation/solidity-analyzer-linux-arm64-gnu": "0.1.1",
+ "@nomicfoundation/solidity-analyzer-linux-arm64-musl": "0.1.1",
+ "@nomicfoundation/solidity-analyzer-linux-x64-gnu": "0.1.1",
+ "@nomicfoundation/solidity-analyzer-linux-x64-musl": "0.1.1",
+ "@nomicfoundation/solidity-analyzer-win32-arm64-msvc": "0.1.1",
+ "@nomicfoundation/solidity-analyzer-win32-ia32-msvc": "0.1.1",
+ "@nomicfoundation/solidity-analyzer-win32-x64-msvc": "0.1.1"
+ }
+ },
+ "node_modules/@nomicfoundation/solidity-analyzer-darwin-arm64": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-darwin-arm64/-/solidity-analyzer-darwin-arm64-0.1.1.tgz",
+ "integrity": "sha512-KcTodaQw8ivDZyF+D76FokN/HdpgGpfjc/gFCImdLUyqB6eSWVaZPazMbeAjmfhx3R0zm/NYVzxwAokFKgrc0w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@nomicfoundation/solidity-analyzer-darwin-x64": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-darwin-x64/-/solidity-analyzer-darwin-x64-0.1.1.tgz",
+ "integrity": "sha512-XhQG4BaJE6cIbjAVtzGOGbK3sn1BO9W29uhk9J8y8fZF1DYz0Doj8QDMfpMu+A6TjPDs61lbsmeYodIDnfveSA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@nomicfoundation/solidity-analyzer-freebsd-x64": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-freebsd-x64/-/solidity-analyzer-freebsd-x64-0.1.1.tgz",
+ "integrity": "sha512-GHF1VKRdHW3G8CndkwdaeLkVBi5A9u2jwtlS7SLhBc8b5U/GcoL39Q+1CSO3hYqePNP+eV5YI7Zgm0ea6kMHoA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@nomicfoundation/solidity-analyzer-linux-arm64-gnu": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-gnu/-/solidity-analyzer-linux-arm64-gnu-0.1.1.tgz",
+ "integrity": "sha512-g4Cv2fO37ZsUENQ2vwPnZc2zRenHyAxHcyBjKcjaSmmkKrFr64yvzeNO8S3GBFCo90rfochLs99wFVGT/0owpg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@nomicfoundation/solidity-analyzer-linux-arm64-musl": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-musl/-/solidity-analyzer-linux-arm64-musl-0.1.1.tgz",
+ "integrity": "sha512-WJ3CE5Oek25OGE3WwzK7oaopY8xMw9Lhb0mlYuJl/maZVo+WtP36XoQTb7bW/i8aAdHW5Z+BqrHMux23pvxG3w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@nomicfoundation/solidity-analyzer-linux-x64-gnu": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-gnu/-/solidity-analyzer-linux-x64-gnu-0.1.1.tgz",
+ "integrity": "sha512-5WN7leSr5fkUBBjE4f3wKENUy9HQStu7HmWqbtknfXkkil+eNWiBV275IOlpXku7v3uLsXTOKpnnGHJYI2qsdA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@nomicfoundation/solidity-analyzer-linux-x64-musl": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-musl/-/solidity-analyzer-linux-x64-musl-0.1.1.tgz",
+ "integrity": "sha512-KdYMkJOq0SYPQMmErv/63CwGwMm5XHenEna9X9aB8mQmhDBrYrlAOSsIPgFCUSL0hjxE3xHP65/EPXR/InD2+w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@nomicfoundation/solidity-analyzer-win32-arm64-msvc": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-arm64-msvc/-/solidity-analyzer-win32-arm64-msvc-0.1.1.tgz",
+ "integrity": "sha512-VFZASBfl4qiBYwW5xeY20exWhmv6ww9sWu/krWSesv3q5hA0o1JuzmPHR4LPN6SUZj5vcqci0O6JOL8BPw+APg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@nomicfoundation/solidity-analyzer-win32-ia32-msvc": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-ia32-msvc/-/solidity-analyzer-win32-ia32-msvc-0.1.1.tgz",
+ "integrity": "sha512-JnFkYuyCSA70j6Si6cS1A9Gh1aHTEb8kOTBApp/c7NRTFGNMH8eaInKlyuuiIbvYFhlXW4LicqyYuWNNq9hkpQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@nomicfoundation/solidity-analyzer-win32-x64-msvc": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-x64-msvc/-/solidity-analyzer-win32-x64-msvc-0.1.1.tgz",
+ "integrity": "sha512-HrVJr6+WjIXGnw3Q9u6KQcbZCtk0caVWhCdFADySvRyUxJ8PnzlaP+MhwNE8oyT8OZ6ejHBRrrgjSqDCFXGirw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@nomiclabs/hardhat-ethers": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/@nomiclabs/hardhat-ethers/-/hardhat-ethers-2.2.3.tgz",
+ "integrity": "sha512-YhzPdzb612X591FOe68q+qXVXGG2ANZRvDo0RRUtimev85rCrAlv/TLMEZw5c+kq9AbzocLTVX/h2jVIFPL9Xg==",
+ "dev": true,
+ "peer": true,
+ "peerDependencies": {
+ "ethers": "^5.0.0",
+ "hardhat": "^2.0.0"
+ }
+ },
+ "node_modules/@nomiclabs/hardhat-etherscan": {
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/@nomiclabs/hardhat-etherscan/-/hardhat-etherscan-3.1.7.tgz",
+ "integrity": "sha512-tZ3TvSgpvsQ6B6OGmo1/Au6u8BrAkvs1mIC/eURA3xgIfznUZBhmpne8hv7BXUzw9xNL3fXdpOYgOQlVMTcoHQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@ethersproject/abi": "^5.1.2",
+ "@ethersproject/address": "^5.0.2",
+ "cbor": "^8.1.0",
+ "chalk": "^2.4.2",
+ "debug": "^4.1.1",
+ "fs-extra": "^7.0.1",
+ "lodash": "^4.17.11",
+ "semver": "^6.3.0",
+ "table": "^6.8.0",
+ "undici": "^5.14.0"
+ },
+ "peerDependencies": {
+ "hardhat": "^2.0.4"
+ }
+ },
+ "node_modules/@openzeppelin/contracts": {
+ "version": "4.9.2",
+ "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.9.2.tgz",
+ "integrity": "sha512-mO+y6JaqXjWeMh9glYVzVu8HYPGknAAnWyxTRhGeckOruyXQMNnlcW6w/Dx9ftLeIQk6N+ZJFuVmTwF7lEIFrg==",
+ "dev": true
+ },
+ "node_modules/@openzeppelin/contracts-upgradeable": {
+ "version": "4.9.2",
+ "resolved": "https://registry.npmjs.org/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.9.2.tgz",
+ "integrity": "sha512-siviV3PZV/fHfPaoIC51rf1Jb6iElkYWnNYZ0leO23/ukXuvOyoC/ahy8jqiV7g+++9Nuo3n/rk5ajSN/+d/Sg==",
+ "dev": true
+ },
+ "node_modules/@openzeppelin/defender-base-client": {
+ "version": "1.47.0",
+ "resolved": "https://registry.npmjs.org/@openzeppelin/defender-base-client/-/defender-base-client-1.47.0.tgz",
+ "integrity": "sha512-y9dDm+gX0MHHEn17W7f7oO3X083JAVMk3YcuXHavSE7kjiCLoFOaZ23joYqoHeaccL10nGt7KOOzZ0sh9iJHTQ==",
+ "dev": true,
+ "dependencies": {
+ "amazon-cognito-identity-js": "^6.0.1",
+ "async-retry": "^1.3.3",
+ "axios": "^1.4.0",
+ "lodash": "^4.17.19",
+ "node-fetch": "^2.6.0"
+ }
+ },
+ "node_modules/@openzeppelin/hardhat-upgrades": {
+ "version": "1.28.0",
+ "resolved": "https://registry.npmjs.org/@openzeppelin/hardhat-upgrades/-/hardhat-upgrades-1.28.0.tgz",
+ "integrity": "sha512-7sb/Jf+X+uIufOBnmHR0FJVWuxEs2lpxjJnLNN6eCJCP8nD0v+Ot5lTOW2Qb/GFnh+fLvJtEkhkowz4ZQ57+zQ==",
+ "dev": true,
+ "dependencies": {
+ "@openzeppelin/defender-base-client": "^1.46.0",
+ "@openzeppelin/platform-deploy-client": "^0.8.0",
+ "@openzeppelin/upgrades-core": "^1.27.0",
+ "chalk": "^4.1.0",
+ "debug": "^4.1.1",
+ "proper-lockfile": "^4.1.1"
+ },
+ "bin": {
+ "migrate-oz-cli-project": "dist/scripts/migrate-oz-cli-project.js"
+ },
+ "peerDependencies": {
+ "@nomiclabs/hardhat-ethers": "^2.0.0",
+ "@nomiclabs/hardhat-etherscan": "^3.1.0",
+ "ethers": "^5.0.5",
+ "hardhat": "^2.0.2"
+ },
+ "peerDependenciesMeta": {
+ "@nomiclabs/harhdat-etherscan": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@openzeppelin/hardhat-upgrades/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/@openzeppelin/hardhat-upgrades/node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/@openzeppelin/hardhat-upgrades/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/@openzeppelin/hardhat-upgrades/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/@openzeppelin/hardhat-upgrades/node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@openzeppelin/hardhat-upgrades/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@openzeppelin/platform-deploy-client": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/@openzeppelin/platform-deploy-client/-/platform-deploy-client-0.8.0.tgz",
+ "integrity": "sha512-POx3AsnKwKSV/ZLOU/gheksj0Lq7Is1q2F3pKmcFjGZiibf+4kjGxr4eSMrT+2qgKYZQH1ZLQZ+SkbguD8fTvA==",
+ "dev": true,
+ "dependencies": {
+ "@ethersproject/abi": "^5.6.3",
+ "@openzeppelin/defender-base-client": "^1.46.0",
+ "axios": "^0.21.2",
+ "lodash": "^4.17.19",
+ "node-fetch": "^2.6.0"
+ }
+ },
+ "node_modules/@openzeppelin/platform-deploy-client/node_modules/axios": {
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
+ "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
+ "dev": true,
+ "dependencies": {
+ "follow-redirects": "^1.14.0"
+ }
+ },
+ "node_modules/@openzeppelin/upgrades-core": {
+ "version": "1.27.3",
+ "resolved": "https://registry.npmjs.org/@openzeppelin/upgrades-core/-/upgrades-core-1.27.3.tgz",
+ "integrity": "sha512-IqlSMUkno1XKF4L46aUqZ4BqHxj4dF0BRGrFcKeG2Q0vrsKoazhY67JG9bO+wMYG4zxl6jgmG0bd5ef9HLcLmw==",
+ "dev": true,
+ "dependencies": {
+ "cbor": "^8.0.0",
+ "chalk": "^4.1.0",
+ "compare-versions": "^6.0.0",
+ "debug": "^4.1.1",
+ "ethereumjs-util": "^7.0.3",
+ "minimist": "^1.2.7",
+ "proper-lockfile": "^4.1.1",
+ "solidity-ast": "^0.4.15"
+ },
+ "bin": {
+ "openzeppelin-upgrades-core": "dist/cli/cli.js"
+ }
+ },
+ "node_modules/@openzeppelin/upgrades-core/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/@openzeppelin/upgrades-core/node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/@openzeppelin/upgrades-core/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/@openzeppelin/upgrades-core/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/@openzeppelin/upgrades-core/node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@openzeppelin/upgrades-core/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@scure/base": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz",
+ "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ ]
+ },
+ "node_modules/@scure/bip32": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.1.5.tgz",
+ "integrity": "sha512-XyNh1rB0SkEqd3tXcXMi+Xe1fvg+kUIcoRIEujP1Jgv7DqW2r9lg3Ah0NkFaCs9sTkQAQA8kw7xiRXzENi9Rtw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ ],
+ "dependencies": {
+ "@noble/hashes": "~1.2.0",
+ "@noble/secp256k1": "~1.7.0",
+ "@scure/base": "~1.1.0"
+ }
+ },
+ "node_modules/@scure/bip39": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.1.1.tgz",
+ "integrity": "sha512-t+wDck2rVkh65Hmv280fYdVdY25J9YeEUIgn2LG1WM6gxFkGzcksoDiUkWVpVp3Oex9xGC68JU2dSbUfwZ2jPg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ ],
+ "dependencies": {
+ "@noble/hashes": "~1.2.0",
+ "@scure/base": "~1.1.0"
+ }
+ },
+ "node_modules/@sentry/core": {
+ "version": "5.30.0",
+ "resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.30.0.tgz",
+ "integrity": "sha512-TmfrII8w1PQZSZgPpUESqjB+jC6MvZJZdLtE/0hZ+SrnKhW3x5WlYLvTXZpcWePYBku7rl2wn1RZu6uT0qCTeg==",
+ "dev": true,
+ "dependencies": {
+ "@sentry/hub": "5.30.0",
+ "@sentry/minimal": "5.30.0",
+ "@sentry/types": "5.30.0",
+ "@sentry/utils": "5.30.0",
+ "tslib": "^1.9.3"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@sentry/hub": {
+ "version": "5.30.0",
+ "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.30.0.tgz",
+ "integrity": "sha512-2tYrGnzb1gKz2EkMDQcfLrDTvmGcQPuWxLnJKXJvYTQDGLlEvi2tWz1VIHjunmOvJrB5aIQLhm+dcMRwFZDCqQ==",
+ "dev": true,
+ "dependencies": {
+ "@sentry/types": "5.30.0",
+ "@sentry/utils": "5.30.0",
+ "tslib": "^1.9.3"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@sentry/minimal": {
+ "version": "5.30.0",
+ "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.30.0.tgz",
+ "integrity": "sha512-BwWb/owZKtkDX+Sc4zCSTNcvZUq7YcH3uAVlmh/gtR9rmUvbzAA3ewLuB3myi4wWRAMEtny6+J/FN/x+2wn9Xw==",
+ "dev": true,
+ "dependencies": {
+ "@sentry/hub": "5.30.0",
+ "@sentry/types": "5.30.0",
+ "tslib": "^1.9.3"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@sentry/node": {
+ "version": "5.30.0",
+ "resolved": "https://registry.npmjs.org/@sentry/node/-/node-5.30.0.tgz",
+ "integrity": "sha512-Br5oyVBF0fZo6ZS9bxbJZG4ApAjRqAnqFFurMVJJdunNb80brh7a5Qva2kjhm+U6r9NJAB5OmDyPkA1Qnt+QVg==",
+ "dev": true,
+ "dependencies": {
+ "@sentry/core": "5.30.0",
+ "@sentry/hub": "5.30.0",
+ "@sentry/tracing": "5.30.0",
+ "@sentry/types": "5.30.0",
+ "@sentry/utils": "5.30.0",
+ "cookie": "^0.4.1",
+ "https-proxy-agent": "^5.0.0",
+ "lru_map": "^0.3.3",
+ "tslib": "^1.9.3"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@sentry/tracing": {
+ "version": "5.30.0",
+ "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-5.30.0.tgz",
+ "integrity": "sha512-dUFowCr0AIMwiLD7Fs314Mdzcug+gBVo/+NCMyDw8tFxJkwWAKl7Qa2OZxLQ0ZHjakcj1hNKfCQJ9rhyfOl4Aw==",
+ "dev": true,
+ "dependencies": {
+ "@sentry/hub": "5.30.0",
+ "@sentry/minimal": "5.30.0",
+ "@sentry/types": "5.30.0",
+ "@sentry/utils": "5.30.0",
+ "tslib": "^1.9.3"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@sentry/types": {
+ "version": "5.30.0",
+ "resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.30.0.tgz",
+ "integrity": "sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@sentry/utils": {
+ "version": "5.30.0",
+ "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.30.0.tgz",
+ "integrity": "sha512-zaYmoH0NWWtvnJjC9/CBseXMtKHm/tm40sz3YfJRxeQjyzRqNQPgivpd9R/oDJCYj999mzdW382p/qi2ypjLww==",
+ "dev": true,
+ "dependencies": {
+ "@sentry/types": "5.30.0",
+ "tslib": "^1.9.3"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@smithy/types": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.0.2.tgz",
+ "integrity": "sha512-wcymEjIXQ9+NEfE5Yt5TInAqe1o4n+Nh+rh00AwoazppmUt8tdo6URhc5gkDcOYrcvlDVAZE7uG69nDpEGUKxw==",
+ "dev": true,
+ "dependencies": {
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/types/node_modules/tslib": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz",
+ "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==",
+ "dev": true
+ },
+ "node_modules/@solidity-parser/parser": {
+ "version": "0.14.5",
+ "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.14.5.tgz",
+ "integrity": "sha512-6dKnHZn7fg/iQATVEzqyUOyEidbn05q7YA2mQ9hC0MMXhhV3/JrsxmFSYZAcr7j1yUP700LLhTruvJ3MiQmjJg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "antlr4ts": "^0.5.0-alpha.4"
+ }
+ },
+ "node_modules/@tsconfig/node10": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
+ "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/@tsconfig/node12": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
+ "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/@tsconfig/node14": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
+ "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/@tsconfig/node16": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
+ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/@typechain/ethers-v5": {
+ "version": "10.2.1",
+ "resolved": "https://registry.npmjs.org/@typechain/ethers-v5/-/ethers-v5-10.2.1.tgz",
+ "integrity": "sha512-n3tQmCZjRE6IU4h6lqUGiQ1j866n5MTCBJreNEHHVWXa2u9GJTaeYyU1/k+1qLutkyw+sS6VAN+AbeiTqsxd/A==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "lodash": "^4.17.15",
+ "ts-essentials": "^7.0.1"
+ },
+ "peerDependencies": {
+ "@ethersproject/abi": "^5.0.0",
+ "@ethersproject/providers": "^5.0.0",
+ "ethers": "^5.1.3",
+ "typechain": "^8.1.1",
+ "typescript": ">=4.3.0"
+ }
+ },
+ "node_modules/@typechain/hardhat": {
+ "version": "6.1.6",
+ "resolved": "https://registry.npmjs.org/@typechain/hardhat/-/hardhat-6.1.6.tgz",
+ "integrity": "sha512-BiVnegSs+ZHVymyidtK472syodx1sXYlYJJixZfRstHVGYTi8V1O7QG4nsjyb0PC/LORcq7sfBUcHto1y6UgJA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "fs-extra": "^9.1.0"
+ },
+ "peerDependencies": {
+ "@ethersproject/abi": "^5.4.7",
+ "@ethersproject/providers": "^5.4.7",
+ "@typechain/ethers-v5": "^10.2.1",
+ "ethers": "^5.4.7",
+ "hardhat": "^2.9.9",
+ "typechain": "^8.1.1"
+ }
+ },
+ "node_modules/@typechain/hardhat/node_modules/fs-extra": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
+ "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "at-least-node": "^1.0.0",
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@typechain/hardhat/node_modules/jsonfile": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/@typechain/hardhat/node_modules/universalify": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
+ "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/@types/bn.js": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.1.tgz",
+ "integrity": "sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/chai": {
+ "version": "4.3.5",
+ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.5.tgz",
+ "integrity": "sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/@types/chai-as-promised": {
+ "version": "7.1.5",
+ "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.5.tgz",
+ "integrity": "sha512-jStwss93SITGBwt/niYrkf2C+/1KTeZCZl1LaeezTlqppAKeoQC7jxyqYuP72sxBGKCIbw7oHgbYssIRzT5FCQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@types/chai": "*"
+ }
+ },
+ "node_modules/@types/concat-stream": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@types/concat-stream/-/concat-stream-1.6.1.tgz",
+ "integrity": "sha512-eHE4cQPoj6ngxBZMvVf6Hw7Mh4jMW4U9lpGmS5GBPB9RYxlFg+CHaVN7ErNY4W9XfLIEn20b4VDYaIrbq0q4uA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/diff": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/@types/diff/-/diff-5.0.3.tgz",
+ "integrity": "sha512-amrLbRqTU9bXMCc6uX0sWpxsQzRIo9z6MJPkH1pkez/qOxuqSZVuryJAWoBRq94CeG8JxY+VK4Le9HtjQR5T9A==",
+ "dev": true
+ },
+ "node_modules/@types/form-data": {
+ "version": "0.0.33",
+ "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-0.0.33.tgz",
+ "integrity": "sha512-8BSvG1kGm83cyJITQMZSulnl6QV8jqAGreJsc5tPu1Jq0vTSOiY/k24Wx82JRpWwZSqrala6sd5rWi6aNXvqcw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/glob": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz",
+ "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@types/minimatch": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.12",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz",
+ "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==",
+ "dev": true
+ },
+ "node_modules/@types/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw==",
+ "dev": true
+ },
+ "node_modules/@types/minimatch": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz",
+ "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/@types/mocha": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz",
+ "integrity": "sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/@types/node": {
+ "version": "20.4.5",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.5.tgz",
+ "integrity": "sha512-rt40Nk13II9JwQBdeYqmbn2Q6IVTA5uPhvSO+JVqdXw/6/4glI6oR9ezty/A9Hg5u7JH4OmYmuQ+XvjKm0Datg==",
+ "dev": true
+ },
+ "node_modules/@types/npmlog": {
+ "version": "4.1.4",
+ "resolved": "https://registry.npmjs.org/@types/npmlog/-/npmlog-4.1.4.tgz",
+ "integrity": "sha512-WKG4gTr8przEZBiJ5r3s8ZIAoMXNbOgQ+j/d5O4X3x6kZJRLNvyUJuUK/KoG3+8BaOHPhp2m7WC6JKKeovDSzQ==",
+ "dev": true
+ },
+ "node_modules/@types/pbkdf2": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@types/pbkdf2/-/pbkdf2-3.1.0.tgz",
+ "integrity": "sha512-Cf63Rv7jCQ0LaL8tNXmEyqTHuIJxRdlS5vMh1mj5voN4+QFhVZnlZruezqpWYDiJ8UTzhP0VmeLXCmBk66YrMQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/prettier": {
+ "version": "2.7.3",
+ "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz",
+ "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/@types/qs": {
+ "version": "6.9.7",
+ "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
+ "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/@types/readable-stream": {
+ "version": "2.3.15",
+ "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-2.3.15.tgz",
+ "integrity": "sha512-oM5JSKQCcICF1wvGgmecmHldZ48OZamtMxcGGVICOJA8o8cahXC1zEVAif8iwoc5j8etxFaRFnf095+CDsuoFQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*",
+ "safe-buffer": "~5.1.1"
+ }
+ },
+ "node_modules/@types/readable-stream/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "dev": true
+ },
+ "node_modules/@types/secp256k1": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.3.tgz",
+ "integrity": "sha512-Da66lEIFeIz9ltsdMZcpQvmrmmoqrfju8pm1BH8WbYjZSwUgCwXLb9C+9XYogwBITnbsSaMdVPb2ekf7TV+03w==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/semver": {
+ "version": "7.5.0",
+ "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz",
+ "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==",
+ "dev": true
+ },
+ "node_modules/@types/yargs": {
+ "version": "17.0.24",
+ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz",
+ "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==",
+ "dev": true,
+ "dependencies": {
+ "@types/yargs-parser": "*"
+ }
+ },
+ "node_modules/@types/yargs-parser": {
+ "version": "21.0.0",
+ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz",
+ "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==",
+ "dev": true
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "5.62.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz",
+ "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==",
+ "dev": true,
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.4.0",
+ "@typescript-eslint/scope-manager": "5.62.0",
+ "@typescript-eslint/type-utils": "5.62.0",
+ "@typescript-eslint/utils": "5.62.0",
+ "debug": "^4.3.4",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.2.0",
+ "natural-compare-lite": "^1.4.0",
+ "semver": "^7.3.7",
+ "tsutils": "^3.21.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^5.0.0",
+ "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin/node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": {
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+ "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+ "dev": true,
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin/node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "5.62.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz",
+ "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "5.62.0",
+ "@typescript-eslint/types": "5.62.0",
+ "@typescript-eslint/typescript-estree": "5.62.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "5.62.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz",
+ "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "5.62.0",
+ "@typescript-eslint/visitor-keys": "5.62.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "5.62.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz",
+ "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/typescript-estree": "5.62.0",
+ "@typescript-eslint/utils": "5.62.0",
+ "debug": "^4.3.4",
+ "tsutils": "^3.21.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "*"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "5.62.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz",
+ "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "5.62.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz",
+ "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "5.62.0",
+ "@typescript-eslint/visitor-keys": "5.62.0",
+ "debug": "^4.3.4",
+ "globby": "^11.1.0",
+ "is-glob": "^4.0.3",
+ "semver": "^7.3.7",
+ "tsutils": "^3.21.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+ "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+ "dev": true,
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "5.62.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz",
+ "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==",
+ "dev": true,
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@types/json-schema": "^7.0.9",
+ "@types/semver": "^7.3.12",
+ "@typescript-eslint/scope-manager": "5.62.0",
+ "@typescript-eslint/types": "5.62.0",
+ "@typescript-eslint/typescript-estree": "5.62.0",
+ "eslint-scope": "^5.1.1",
+ "semver": "^7.3.7"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/utils/node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@typescript-eslint/utils/node_modules/semver": {
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+ "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+ "dev": true,
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@typescript-eslint/utils/node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "5.62.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz",
+ "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "5.62.0",
+ "eslint-visitor-keys": "^3.3.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/abbrev": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz",
+ "integrity": "sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/abort-controller": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
+ "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
+ "dev": true,
+ "dependencies": {
+ "event-target-shim": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=6.5"
+ }
+ },
+ "node_modules/abstract-level": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/abstract-level/-/abstract-level-1.0.3.tgz",
+ "integrity": "sha512-t6jv+xHy+VYwc4xqZMn2Pa9DjcdzvzZmQGRjTFc8spIbRGHgBrEKbPq+rYXc7CCo0lxgYvSgKVg9qZAhpVQSjA==",
+ "dev": true,
+ "dependencies": {
+ "buffer": "^6.0.3",
+ "catering": "^2.1.0",
+ "is-buffer": "^2.0.5",
+ "level-supports": "^4.0.0",
+ "level-transcoder": "^1.0.1",
+ "module-error": "^1.0.1",
+ "queue-microtask": "^1.2.3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/abstract-level/node_modules/buffer": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
+ "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.2.1"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.10.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
+ "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==",
+ "dev": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/acorn-walk": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
+ "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/address": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz",
+ "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/adm-zip": {
+ "version": "0.4.16",
+ "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz",
+ "integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.3.0"
+ }
+ },
+ "node_modules/aes-js": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz",
+ "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==",
+ "dev": true
+ },
+ "node_modules/agent-base": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+ "dev": true,
+ "dependencies": {
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/aggregate-error": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
+ "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
+ "dev": true,
+ "dependencies": {
+ "clean-stack": "^2.0.0",
+ "indent-string": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/amazon-cognito-identity-js": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/amazon-cognito-identity-js/-/amazon-cognito-identity-js-6.3.1.tgz",
+ "integrity": "sha512-PxBdufgS8uZShrcIFAsRjmqNXsh/4fXOWUGQOUhKLHWWK1pcp/y+VeFF48avXIWefM8XwsT3JlN6m9J2eHt4LA==",
+ "dev": true,
+ "dependencies": {
+ "@aws-crypto/sha256-js": "1.2.2",
+ "buffer": "4.9.2",
+ "fast-base64-decode": "^1.0.0",
+ "isomorphic-unfetch": "^3.0.0",
+ "js-cookie": "^2.2.1"
+ }
+ },
+ "node_modules/amdefine": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
+ "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==",
+ "dev": true,
+ "optional": true,
+ "peer": true,
+ "engines": {
+ "node": ">=0.4.2"
+ }
+ },
+ "node_modules/ansi-colors": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
+ "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/ansi-escapes": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
+ "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
+ "dev": true,
+ "dependencies": {
+ "type-fest": "^0.21.3"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ansi-escapes/node_modules/type-fest": {
+ "version": "0.21.3",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
+ "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/antlr4": {
+ "version": "4.13.0",
+ "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.13.0.tgz",
+ "integrity": "sha512-zooUbt+UscjnWyOrsuY/tVFL4rwrAGwOivpQmvmUDE22hy/lUA467Rc1rcixyRwcRUIXFYBwv7+dClDSHdmmew==",
+ "dev": true,
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/antlr4ts": {
+ "version": "0.5.0-alpha.4",
+ "resolved": "https://registry.npmjs.org/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz",
+ "integrity": "sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==",
+ "dev": true
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/aproba": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
+ "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==",
+ "dev": true
+ },
+ "node_modules/are-we-there-yet": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-4.0.1.tgz",
+ "integrity": "sha512-2zuA+jpOYBRgoBCfa+fB87Rk0oGJjDX6pxGzqH6f33NzUhG25Xur6R0u0Z9VVAq8Z5JvQpQI6j6rtonuivC8QA==",
+ "dev": true,
+ "dependencies": {
+ "delegates": "^1.0.0",
+ "readable-stream": "^4.1.0"
+ },
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
+ "node_modules/are-we-there-yet/node_modules/buffer": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
+ "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.2.1"
+ }
+ },
+ "node_modules/are-we-there-yet/node_modules/readable-stream": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.4.2.tgz",
+ "integrity": "sha512-Lk/fICSyIhodxy1IDK2HazkeGjSmezAWX2egdtJnYhtzKEsBPJowlI6F6LPb5tqIQILrMbx22S5o3GuJavPusA==",
+ "dev": true,
+ "dependencies": {
+ "abort-controller": "^3.0.0",
+ "buffer": "^6.0.3",
+ "events": "^3.3.0",
+ "process": "^0.11.10",
+ "string_decoder": "^1.3.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/arg": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
+ "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true
+ },
+ "node_modules/array-back": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz",
+ "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/array-buffer-byte-length": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz",
+ "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "is-array-buffer": "^3.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array-union": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
+ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/array-uniq": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
+ "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/array.prototype.reduce": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.5.tgz",
+ "integrity": "sha512-kDdugMl7id9COE8R7MHF5jWk7Dqt/fs4Pv+JXoICnYwqpjjjbUurz6w5fT5IG6brLdJhv6/VoHB0H7oyIBXd+Q==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4",
+ "es-array-method-boxes-properly": "^1.0.0",
+ "is-string": "^1.0.7"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/arraybuffer.prototype.slice": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.1.tgz",
+ "integrity": "sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "array-buffer-byte-length": "^1.0.0",
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "get-intrinsic": "^1.2.1",
+ "is-array-buffer": "^3.0.2",
+ "is-shared-array-buffer": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/asap": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
+ "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/asn1": {
+ "version": "0.2.6",
+ "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
+ "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "safer-buffer": "~2.1.0"
+ }
+ },
+ "node_modules/assert-plus": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+ "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/assertion-error": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
+ "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/ast-parents": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/ast-parents/-/ast-parents-0.0.1.tgz",
+ "integrity": "sha512-XHusKxKz3zoYk1ic8Un640joHbFMhbqneyoZfoKnEGtf2ey9Uh/IdpcQplODdO/kENaMIWsD0nJm4+wX3UNLHA==",
+ "dev": true
+ },
+ "node_modules/astral-regex": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
+ "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/async": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
+ "integrity": "sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/async-retry": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz",
+ "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==",
+ "dev": true,
+ "dependencies": {
+ "retry": "0.13.1"
+ }
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "dev": true
+ },
+ "node_modules/at-least-node": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
+ "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/available-typed-arrays": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
+ "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/aws-sign2": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
+ "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/aws4": {
+ "version": "1.12.0",
+ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz",
+ "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/axios": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz",
+ "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==",
+ "dev": true,
+ "dependencies": {
+ "follow-redirects": "^1.15.0",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true
+ },
+ "node_modules/base-x": {
+ "version": "3.0.9",
+ "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz",
+ "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==",
+ "dev": true,
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/bcrypt-pbkdf": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
+ "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "tweetnacl": "^0.14.3"
+ }
+ },
+ "node_modules/bcrypt-pbkdf/node_modules/tweetnacl": {
+ "version": "0.14.5",
+ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+ "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/bech32": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz",
+ "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==",
+ "dev": true
+ },
+ "node_modules/bigint-crypto-utils": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/bigint-crypto-utils/-/bigint-crypto-utils-3.3.0.tgz",
+ "integrity": "sha512-jOTSb+drvEDxEq6OuUybOAv/xxoh3cuYRUIPyu8sSHQNKM303UQ2R1DAo45o1AkcIXw6fzbaFI1+xGGdaXs2lg==",
+ "dev": true,
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/blakejs": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz",
+ "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==",
+ "dev": true
+ },
+ "node_modules/bn.js": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz",
+ "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==",
+ "dev": true
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
+ "dependencies": {
+ "fill-range": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/brorand": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
+ "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==",
+ "dev": true
+ },
+ "node_modules/browser-level": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/browser-level/-/browser-level-1.0.1.tgz",
+ "integrity": "sha512-XECYKJ+Dbzw0lbydyQuJzwNXtOpbMSq737qxJN11sIRTErOMShvDpbzTlgju7orJKvx4epULolZAuJGLzCmWRQ==",
+ "dev": true,
+ "dependencies": {
+ "abstract-level": "^1.0.2",
+ "catering": "^2.1.1",
+ "module-error": "^1.0.2",
+ "run-parallel-limit": "^1.1.0"
+ }
+ },
+ "node_modules/browser-stdout": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
+ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
+ "dev": true
+ },
+ "node_modules/browserify-aes": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
+ "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
+ "dev": true,
+ "dependencies": {
+ "buffer-xor": "^1.0.3",
+ "cipher-base": "^1.0.0",
+ "create-hash": "^1.1.0",
+ "evp_bytestokey": "^1.0.3",
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/bs58": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz",
+ "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==",
+ "dev": true,
+ "dependencies": {
+ "base-x": "^3.0.2"
+ }
+ },
+ "node_modules/bs58check": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz",
+ "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==",
+ "dev": true,
+ "dependencies": {
+ "bs58": "^4.0.0",
+ "create-hash": "^1.1.0",
+ "safe-buffer": "^5.1.2"
+ }
+ },
+ "node_modules/buffer": {
+ "version": "4.9.2",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz",
+ "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==",
+ "dev": true,
+ "dependencies": {
+ "base64-js": "^1.0.2",
+ "ieee754": "^1.1.4",
+ "isarray": "^1.0.0"
+ }
+ },
+ "node_modules/buffer-from": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+ "dev": true
+ },
+ "node_modules/buffer-xor": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
+ "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==",
+ "dev": true
+ },
+ "node_modules/busboy": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
+ "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
+ "dev": true,
+ "dependencies": {
+ "streamsearch": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=10.16.0"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/call-bind": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
+ "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "function-bind": "^1.1.1",
+ "get-intrinsic": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/camelcase": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/case": {
+ "version": "1.6.3",
+ "resolved": "https://registry.npmjs.org/case/-/case-1.6.3.tgz",
+ "integrity": "sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/caseless": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
+ "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/catering": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/catering/-/catering-2.1.1.tgz",
+ "integrity": "sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/cbor": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/cbor/-/cbor-8.1.0.tgz",
+ "integrity": "sha512-DwGjNW9omn6EwP70aXsn7FQJx5kO12tX0bZkaTjzdVFM6/7nhA4t0EENocKGx6D2Bch9PE2KzCUf5SceBdeijg==",
+ "dev": true,
+ "dependencies": {
+ "nofilter": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=12.19"
+ }
+ },
+ "node_modules/chai": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz",
+ "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "assertion-error": "^1.1.0",
+ "check-error": "^1.0.2",
+ "deep-eql": "^4.1.2",
+ "get-func-name": "^2.0.0",
+ "loupe": "^2.3.1",
+ "pathval": "^1.1.1",
+ "type-detect": "^4.0.5"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/chai-as-promised": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz",
+ "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "check-error": "^1.0.2"
+ },
+ "peerDependencies": {
+ "chai": ">= 2.1.2 < 5"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/charenc": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
+ "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/check-error": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
+ "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
+ "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ ],
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/chokidar/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/ci-info": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
+ "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==",
+ "dev": true
+ },
+ "node_modules/cipher-base": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
+ "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
+ "dev": true,
+ "dependencies": {
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/classic-level": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/classic-level/-/classic-level-1.3.0.tgz",
+ "integrity": "sha512-iwFAJQYtqRTRM0F6L8h4JCt00ZSGdOyqh7yVrhhjrOpFhmBjNlRUey64MCiyo6UmQHMJ+No3c81nujPv+n9yrg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "dependencies": {
+ "abstract-level": "^1.0.2",
+ "catering": "^2.1.0",
+ "module-error": "^1.0.1",
+ "napi-macros": "^2.2.2",
+ "node-gyp-build": "^4.3.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/clean-stack": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
+ "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/cli-table3": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.5.1.tgz",
+ "integrity": "sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "object-assign": "^4.1.0",
+ "string-width": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "optionalDependencies": {
+ "colors": "^1.1.2"
+ }
+ },
+ "node_modules/cliui": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "dev": true,
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/cliui/node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+ "dev": true
+ },
+ "node_modules/color-support": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
+ "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
+ "dev": true,
+ "bin": {
+ "color-support": "bin.js"
+ }
+ },
+ "node_modules/colors": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
+ "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.1.90"
+ }
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dev": true,
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/command-exists": {
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz",
+ "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==",
+ "dev": true
+ },
+ "node_modules/command-line-args": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.1.tgz",
+ "integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "array-back": "^3.1.0",
+ "find-replace": "^3.0.0",
+ "lodash.camelcase": "^4.3.0",
+ "typical": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/command-line-usage": {
+ "version": "6.1.3",
+ "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-6.1.3.tgz",
+ "integrity": "sha512-sH5ZSPr+7UStsloltmDh7Ce5fb8XPlHyoPzTpyyMuYCtervL65+ubVZ6Q61cFtFl62UyJlc8/JwERRbAFPUqgw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "array-back": "^4.0.2",
+ "chalk": "^2.4.2",
+ "table-layout": "^1.0.2",
+ "typical": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/command-line-usage/node_modules/array-back": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz",
+ "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/command-line-usage/node_modules/typical": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz",
+ "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/commander": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz",
+ "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==",
+ "dev": true
+ },
+ "node_modules/compare-versions": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.0.0.tgz",
+ "integrity": "sha512-s2MzYxfRsE9f/ow8hjn7ysa7pod1xhHdQMsgiJtKx6XSNf4x2N1KG4fjrkUmXcP/e9Y2ZX4zB6sHIso0Lm6evQ==",
+ "dev": true
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true
+ },
+ "node_modules/concat-stream": {
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
+ "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
+ "dev": true,
+ "engines": [
+ "node >= 0.8"
+ ],
+ "peer": true,
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "inherits": "^2.0.3",
+ "readable-stream": "^2.2.2",
+ "typedarray": "^0.0.6"
+ }
+ },
+ "node_modules/concat-stream/node_modules/readable-stream": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "node_modules/concat-stream/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/concat-stream/node_modules/string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "node_modules/console-control-strings": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
+ "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==",
+ "dev": true
+ },
+ "node_modules/cookie": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
+ "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/core-util-is": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+ "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/cosmiconfig": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.2.0.tgz",
+ "integrity": "sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==",
+ "dev": true,
+ "dependencies": {
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "parse-json": "^5.0.0",
+ "path-type": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/d-fischer"
+ }
+ },
+ "node_modules/crc-32": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
+ "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
+ "dev": true,
+ "bin": {
+ "crc32": "bin/crc32.njs"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/create-hash": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
+ "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
+ "dev": true,
+ "dependencies": {
+ "cipher-base": "^1.0.1",
+ "inherits": "^2.0.1",
+ "md5.js": "^1.3.4",
+ "ripemd160": "^2.0.1",
+ "sha.js": "^2.4.0"
+ }
+ },
+ "node_modules/create-hmac": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
+ "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
+ "dev": true,
+ "dependencies": {
+ "cipher-base": "^1.0.3",
+ "create-hash": "^1.1.0",
+ "inherits": "^2.0.1",
+ "ripemd160": "^2.0.0",
+ "safe-buffer": "^5.0.1",
+ "sha.js": "^2.4.8"
+ }
+ },
+ "node_modules/create-require": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
+ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+ "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "dev": true,
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/crypt": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
+ "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/dashdash": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+ "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "assert-plus": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/death": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/death/-/death-1.1.0.tgz",
+ "integrity": "sha512-vsV6S4KVHvTGxbEcij7hkWRv0It+sGGWVOM67dQde/o5Xjnr+KmLjxWJii2uEObIrt1CcM9w0Yaovx+iOlIL+w==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decamelize": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz",
+ "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/deep-eql": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz",
+ "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "type-detect": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/deep-extend": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
+ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true
+ },
+ "node_modules/define-properties": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz",
+ "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "has-property-descriptors": "^1.0.0",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/delegates": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
+ "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==",
+ "dev": true
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/detect-port": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.5.1.tgz",
+ "integrity": "sha512-aBzdj76lueB6uUst5iAs7+0H/oOjqI5D16XUWxlWMIMROhcM0rfsNVk93zTngq1dDNpoXRr++Sus7ETAExppAQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "address": "^1.0.1",
+ "debug": "4"
+ },
+ "bin": {
+ "detect": "bin/detect-port.js",
+ "detect-port": "bin/detect-port.js"
+ }
+ },
+ "node_modules/diff": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz",
+ "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
+ "node_modules/difflib": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/difflib/-/difflib-0.2.4.tgz",
+ "integrity": "sha512-9YVwmMb0wQHQNr5J9m6BSj6fk4pfGITGQOOs+D9Fl+INODWFOfvhIU1hNv6GgR1RBoC/9NJcwu77zShxV0kT7w==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "heap": ">= 0.2.0"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/dir-glob": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
+ "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+ "dev": true,
+ "dependencies": {
+ "path-type": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dev": true,
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/dotenv": {
+ "version": "16.3.1",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz",
+ "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/motdotla/dotenv?sponsor=1"
+ }
+ },
+ "node_modules/ecc-jsbn": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
+ "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "jsbn": "~0.1.0",
+ "safer-buffer": "^2.1.0"
+ }
+ },
+ "node_modules/edit-json-file": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/edit-json-file/-/edit-json-file-1.7.0.tgz",
+ "integrity": "sha512-eIkLJ9i4ija7b2TbaLHy3scyjWFLzwM2Wa6kHbV4ppVLcCqn7FzqnO1vmCG3dLrkd+teWE3mvACfv166mO0VZg==",
+ "dev": true,
+ "dependencies": {
+ "find-value": "^1.0.12",
+ "iterate-object": "^1.3.4",
+ "r-json": "^1.2.10",
+ "set-value": "^4.1.0",
+ "w-json": "^1.3.10"
+ }
+ },
+ "node_modules/elliptic": {
+ "version": "6.5.4",
+ "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
+ "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==",
+ "dev": true,
+ "dependencies": {
+ "bn.js": "^4.11.9",
+ "brorand": "^1.1.0",
+ "hash.js": "^1.0.0",
+ "hmac-drbg": "^1.0.1",
+ "inherits": "^2.0.4",
+ "minimalistic-assert": "^1.0.1",
+ "minimalistic-crypto-utils": "^1.0.1"
+ }
+ },
+ "node_modules/elliptic/node_modules/bn.js": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
+ "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
+ "dev": true
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
+ },
+ "node_modules/enquirer": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.0.tgz",
+ "integrity": "sha512-ehu97t6FTYK2I3ZYtnp0BZ9vt0mvEL/cnHBds7Ct6jo9VX1VIkiFhOvVRWh6eblQqd7KOoICIQV+syZ3neXO/Q==",
+ "dev": true,
+ "dependencies": {
+ "ansi-colors": "^4.1.1",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/env-paths": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
+ "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/error-ex": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+ "dev": true,
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "node_modules/es-abstract": {
+ "version": "1.22.1",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.1.tgz",
+ "integrity": "sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "array-buffer-byte-length": "^1.0.0",
+ "arraybuffer.prototype.slice": "^1.0.1",
+ "available-typed-arrays": "^1.0.5",
+ "call-bind": "^1.0.2",
+ "es-set-tostringtag": "^2.0.1",
+ "es-to-primitive": "^1.2.1",
+ "function.prototype.name": "^1.1.5",
+ "get-intrinsic": "^1.2.1",
+ "get-symbol-description": "^1.0.0",
+ "globalthis": "^1.0.3",
+ "gopd": "^1.0.1",
+ "has": "^1.0.3",
+ "has-property-descriptors": "^1.0.0",
+ "has-proto": "^1.0.1",
+ "has-symbols": "^1.0.3",
+ "internal-slot": "^1.0.5",
+ "is-array-buffer": "^3.0.2",
+ "is-callable": "^1.2.7",
+ "is-negative-zero": "^2.0.2",
+ "is-regex": "^1.1.4",
+ "is-shared-array-buffer": "^1.0.2",
+ "is-string": "^1.0.7",
+ "is-typed-array": "^1.1.10",
+ "is-weakref": "^1.0.2",
+ "object-inspect": "^1.12.3",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.4",
+ "regexp.prototype.flags": "^1.5.0",
+ "safe-array-concat": "^1.0.0",
+ "safe-regex-test": "^1.0.0",
+ "string.prototype.trim": "^1.2.7",
+ "string.prototype.trimend": "^1.0.6",
+ "string.prototype.trimstart": "^1.0.6",
+ "typed-array-buffer": "^1.0.0",
+ "typed-array-byte-length": "^1.0.0",
+ "typed-array-byte-offset": "^1.0.0",
+ "typed-array-length": "^1.0.4",
+ "unbox-primitive": "^1.0.2",
+ "which-typed-array": "^1.1.10"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/es-abstract/node_modules/object.assign": {
+ "version": "4.1.4",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
+ "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "has-symbols": "^1.0.3",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/es-array-method-boxes-properly": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz",
+ "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz",
+ "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "get-intrinsic": "^1.1.3",
+ "has": "^1.0.3",
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-to-primitive": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
+ "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "is-callable": "^1.1.4",
+ "is-date-object": "^1.0.1",
+ "is-symbol": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
+ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/escodegen": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz",
+ "integrity": "sha512-yhi5S+mNTOuRvyW4gWlg5W1byMaQGWWSYHXsuFZ7GBo7tpyOwi2EdzMP/QWxh9hwkD2m+wDVHJsxhRIj+v/b/A==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "esprima": "^2.7.1",
+ "estraverse": "^1.9.1",
+ "esutils": "^2.0.2",
+ "optionator": "^0.8.1"
+ },
+ "bin": {
+ "escodegen": "bin/escodegen.js",
+ "esgenerate": "bin/esgenerate.js"
+ },
+ "engines": {
+ "node": ">=0.12.0"
+ },
+ "optionalDependencies": {
+ "source-map": "~0.2.0"
+ }
+ },
+ "node_modules/escodegen/node_modules/estraverse": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz",
+ "integrity": "sha512-25w1fMXQrGdoquWnScXZGckOv+Wes+JDnuN/+7ex3SauFRS72r2lFDec0EKPt2YD1wUJ/IrfEex+9yp4hfSOJA==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/escodegen/node_modules/levn": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
+ "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "prelude-ls": "~1.1.2",
+ "type-check": "~0.3.2"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/escodegen/node_modules/optionator": {
+ "version": "0.8.3",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
+ "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "deep-is": "~0.1.3",
+ "fast-levenshtein": "~2.0.6",
+ "levn": "~0.3.0",
+ "prelude-ls": "~1.1.2",
+ "type-check": "~0.3.2",
+ "word-wrap": "~1.2.3"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/escodegen/node_modules/prelude-ls": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
+ "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/escodegen/node_modules/type-check": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
+ "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "prelude-ls": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "8.45.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.45.0.tgz",
+ "integrity": "sha512-pd8KSxiQpdYRfYa9Wufvdoct3ZPQQuVuU5O6scNgMuOMYuxvH0IGaYK0wUFjo4UYYQQCUndlXiMbnxopwvvTiw==",
+ "dev": true,
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.4.0",
+ "@eslint/eslintrc": "^2.1.0",
+ "@eslint/js": "8.44.0",
+ "@humanwhocodes/config-array": "^0.11.10",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@nodelib/fs.walk": "^1.2.8",
+ "ajv": "^6.10.0",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.3.2",
+ "doctrine": "^3.0.0",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^7.2.0",
+ "eslint-visitor-keys": "^3.4.1",
+ "espree": "^9.6.0",
+ "esquery": "^1.4.2",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "globals": "^13.19.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "is-path-inside": "^3.0.3",
+ "js-yaml": "^4.1.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3",
+ "strip-ansi": "^6.0.1",
+ "text-table": "^0.2.0"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-config-prettier": {
+ "version": "8.8.0",
+ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz",
+ "integrity": "sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==",
+ "dev": true,
+ "bin": {
+ "eslint-config-prettier": "bin/cli.js"
+ },
+ "peerDependencies": {
+ "eslint": ">=7.0.0"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
+ "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
+ "dev": true,
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^4.1.1"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "3.4.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz",
+ "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/eslint/node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/eslint/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/eslint/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/eslint/node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint/node_modules/eslint-scope": {
+ "version": "7.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.1.tgz",
+ "integrity": "sha512-CvefSOsDdaYYvxChovdrPo/ZGt8d5lrJWleAc1diXRKhHGiTYEI26cvo8Kle/wGnsizoCJjK73FMg1/IkIwiNA==",
+ "dev": true,
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint/node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/eslint/node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/eslint/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/espree": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
+ "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
+ "dev": true,
+ "dependencies": {
+ "acorn": "^8.9.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^3.4.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esprima": {
+ "version": "2.7.3",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz",
+ "integrity": "sha512-OarPfz0lFCiW4/AV2Oy1Rp9qu0iusTKqykwTspGCZtPxmF81JR4MmIebvF1F9+UOKth2ZubLQ4XGGaU+hSn99A==",
+ "dev": true,
+ "peer": true,
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz",
+ "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==",
+ "dev": true,
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esquery/node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esrecurse/node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/eth-gas-reporter": {
+ "version": "0.2.25",
+ "resolved": "https://registry.npmjs.org/eth-gas-reporter/-/eth-gas-reporter-0.2.25.tgz",
+ "integrity": "sha512-1fRgyE4xUB8SoqLgN3eDfpDfwEfRxh2Sz1b7wzFbyQA+9TekMmvSjjoRu9SKcSVyK+vLkLIsVbJDsTWjw195OQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@ethersproject/abi": "^5.0.0-beta.146",
+ "@solidity-parser/parser": "^0.14.0",
+ "cli-table3": "^0.5.0",
+ "colors": "1.4.0",
+ "ethereum-cryptography": "^1.0.3",
+ "ethers": "^4.0.40",
+ "fs-readdir-recursive": "^1.1.0",
+ "lodash": "^4.17.14",
+ "markdown-table": "^1.1.3",
+ "mocha": "^7.1.1",
+ "req-cwd": "^2.0.0",
+ "request": "^2.88.0",
+ "request-promise-native": "^1.0.5",
+ "sha1": "^1.1.1",
+ "sync-request": "^6.0.0"
+ },
+ "peerDependencies": {
+ "@codechecks/client": "^0.1.0"
+ },
+ "peerDependenciesMeta": {
+ "@codechecks/client": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eth-gas-reporter/node_modules/ansi-colors": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz",
+ "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/eth-gas-reporter/node_modules/ansi-regex": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
+ "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/eth-gas-reporter/node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "node_modules/eth-gas-reporter/node_modules/bn.js": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
+ "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/eth-gas-reporter/node_modules/camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/eth-gas-reporter/node_modules/chokidar": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz",
+ "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "anymatch": "~3.1.1",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.0",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.2.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.1.1"
+ }
+ },
+ "node_modules/eth-gas-reporter/node_modules/cliui": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
+ "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "string-width": "^3.1.0",
+ "strip-ansi": "^5.2.0",
+ "wrap-ansi": "^5.1.0"
+ }
+ },
+ "node_modules/eth-gas-reporter/node_modules/cliui/node_modules/string-width": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "emoji-regex": "^7.0.1",
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/eth-gas-reporter/node_modules/debug": {
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+ "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+ "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/eth-gas-reporter/node_modules/decamelize": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+ "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/eth-gas-reporter/node_modules/diff": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
+ "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
+ "node_modules/eth-gas-reporter/node_modules/emoji-regex": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+ "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/eth-gas-reporter/node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true,
+ "peer": true,
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/eth-gas-reporter/node_modules/ethereum-cryptography": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-1.2.0.tgz",
+ "integrity": "sha512-6yFQC9b5ug6/17CQpCyE3k9eKBMdhyVjzUy1WkiuY/E4vj/SXDBbCw8QEIaXqf0Mf2SnY6RmpDcwlUmBSS0EJw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@noble/hashes": "1.2.0",
+ "@noble/secp256k1": "1.7.1",
+ "@scure/bip32": "1.1.5",
+ "@scure/bip39": "1.1.1"
+ }
+ },
+ "node_modules/eth-gas-reporter/node_modules/ethers": {
+ "version": "4.0.49",
+ "resolved": "https://registry.npmjs.org/ethers/-/ethers-4.0.49.tgz",
+ "integrity": "sha512-kPltTvWiyu+OktYy1IStSO16i2e7cS9D9OxZ81q2UUaiNPVrm/RTcbxamCXF9VUSKzJIdJV68EAIhTEVBalRWg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "aes-js": "3.0.0",
+ "bn.js": "^4.11.9",
+ "elliptic": "6.5.4",
+ "hash.js": "1.1.3",
+ "js-sha3": "0.5.7",
+ "scrypt-js": "2.0.4",
+ "setimmediate": "1.0.4",
+ "uuid": "2.0.1",
+ "xmlhttprequest": "1.8.0"
+ }
+ },
+ "node_modules/eth-gas-reporter/node_modules/find-up": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "locate-path": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/eth-gas-reporter/node_modules/flat": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.1.tgz",
+ "integrity": "sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "is-buffer": "~2.0.3"
+ },
+ "bin": {
+ "flat": "cli.js"
+ }
+ },
+ "node_modules/eth-gas-reporter/node_modules/fsevents": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz",
+ "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==",
+ "deprecated": "\"Please update to latest v2.3 or v2.2\"",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "peer": true,
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/eth-gas-reporter/node_modules/glob": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
+ "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/eth-gas-reporter/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/eth-gas-reporter/node_modules/hash.js": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz",
+ "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "minimalistic-assert": "^1.0.0"
+ }
+ },
+ "node_modules/eth-gas-reporter/node_modules/js-sha3": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz",
+ "integrity": "sha512-GII20kjaPX0zJ8wzkTbNDYMY7msuZcTWk8S5UOh6806Jq/wz1J8/bnr8uGU0DAUmYDjj2Mr4X1cW8v/GLYnR+g==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/eth-gas-reporter/node_modules/js-yaml": {
+ "version": "3.13.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
+ "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/eth-gas-reporter/node_modules/locate-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "p-locate": "^3.0.0",
+ "path-exists": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/eth-gas-reporter/node_modules/log-symbols": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz",
+ "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "chalk": "^2.4.2"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/eth-gas-reporter/node_modules/minimatch": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/eth-gas-reporter/node_modules/mkdirp": {
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
+ "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "minimist": "^1.2.5"
+ },
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ }
+ },
+ "node_modules/eth-gas-reporter/node_modules/mocha": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.2.0.tgz",
+ "integrity": "sha512-O9CIypScywTVpNaRrCAgoUnJgozpIofjKUYmJhiCIJMiuYnLI6otcb1/kpW9/n/tJODHGZ7i8aLQoDVsMtOKQQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "ansi-colors": "3.2.3",
+ "browser-stdout": "1.3.1",
+ "chokidar": "3.3.0",
+ "debug": "3.2.6",
+ "diff": "3.5.0",
+ "escape-string-regexp": "1.0.5",
+ "find-up": "3.0.0",
+ "glob": "7.1.3",
+ "growl": "1.10.5",
+ "he": "1.2.0",
+ "js-yaml": "3.13.1",
+ "log-symbols": "3.0.0",
+ "minimatch": "3.0.4",
+ "mkdirp": "0.5.5",
+ "ms": "2.1.1",
+ "node-environment-flags": "1.0.6",
+ "object.assign": "4.1.0",
+ "strip-json-comments": "2.0.1",
+ "supports-color": "6.0.0",
+ "which": "1.3.1",
+ "wide-align": "1.1.3",
+ "yargs": "13.3.2",
+ "yargs-parser": "13.1.2",
+ "yargs-unparser": "1.6.0"
+ },
+ "bin": {
+ "_mocha": "bin/_mocha",
+ "mocha": "bin/mocha"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mochajs"
+ }
+ },
+ "node_modules/eth-gas-reporter/node_modules/ms": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+ "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/eth-gas-reporter/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eth-gas-reporter/node_modules/p-locate": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "p-limit": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/eth-gas-reporter/node_modules/path-exists": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+ "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/eth-gas-reporter/node_modules/readdirp": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz",
+ "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/eth-gas-reporter/node_modules/scrypt-js": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-2.0.4.tgz",
+ "integrity": "sha512-4KsaGcPnuhtCZQCxFxN3GVYIhKFPTdLd8PLC552XwbMndtD0cjRFAhDuuydXQ0h08ZfPgzqe6EKHozpuH74iDw==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/eth-gas-reporter/node_modules/setimmediate": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.4.tgz",
+ "integrity": "sha512-/TjEmXQVEzdod/FFskf3o7oOAsGhHf2j1dZqRFbDzq4F3mvvxflIIi4Hd3bLQE9y/CpwqfSQam5JakI/mi3Pog==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/eth-gas-reporter/node_modules/strip-ansi": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "ansi-regex": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/eth-gas-reporter/node_modules/strip-json-comments": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+ "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/eth-gas-reporter/node_modules/supports-color": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz",
+ "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/eth-gas-reporter/node_modules/uuid": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.1.tgz",
+ "integrity": "sha512-nWg9+Oa3qD2CQzHIP4qKUqwNfzKn8P0LtFhotaCTFchsV7ZfDhAybeip/HZVeMIpZi9JgY1E3nUlwaCmZT1sEg==",
+ "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/eth-gas-reporter/node_modules/which": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+ "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "which": "bin/which"
+ }
+ },
+ "node_modules/eth-gas-reporter/node_modules/wide-align": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
+ "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "string-width": "^1.0.2 || 2"
+ }
+ },
+ "node_modules/eth-gas-reporter/node_modules/wrap-ansi": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
+ "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "ansi-styles": "^3.2.0",
+ "string-width": "^3.0.0",
+ "strip-ansi": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/eth-gas-reporter/node_modules/wrap-ansi/node_modules/string-width": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "emoji-regex": "^7.0.1",
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/eth-gas-reporter/node_modules/y18n": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
+ "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/eth-gas-reporter/node_modules/yargs": {
+ "version": "13.3.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz",
+ "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "cliui": "^5.0.0",
+ "find-up": "^3.0.0",
+ "get-caller-file": "^2.0.1",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^2.0.0",
+ "set-blocking": "^2.0.0",
+ "string-width": "^3.0.0",
+ "which-module": "^2.0.0",
+ "y18n": "^4.0.0",
+ "yargs-parser": "^13.1.2"
+ }
+ },
+ "node_modules/eth-gas-reporter/node_modules/yargs-parser": {
+ "version": "13.1.2",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
+ "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "camelcase": "^5.0.0",
+ "decamelize": "^1.2.0"
+ }
+ },
+ "node_modules/eth-gas-reporter/node_modules/yargs-unparser": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz",
+ "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "flat": "^4.1.0",
+ "lodash": "^4.17.15",
+ "yargs": "^13.3.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/eth-gas-reporter/node_modules/yargs/node_modules/string-width": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "emoji-regex": "^7.0.1",
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/ethereum-bloom-filters": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/ethereum-bloom-filters/-/ethereum-bloom-filters-1.0.10.tgz",
+ "integrity": "sha512-rxJ5OFN3RwjQxDcFP2Z5+Q9ho4eIdEmSc2ht0fCu8Se9nbXjZ7/031uXoUYJ87KHCOdVeiUuwSnoS7hmYAGVHA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "js-sha3": "^0.8.0"
+ }
+ },
+ "node_modules/ethereum-cryptography": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz",
+ "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/pbkdf2": "^3.0.0",
+ "@types/secp256k1": "^4.0.1",
+ "blakejs": "^1.1.0",
+ "browserify-aes": "^1.2.0",
+ "bs58check": "^2.1.2",
+ "create-hash": "^1.2.0",
+ "create-hmac": "^1.1.7",
+ "hash.js": "^1.1.7",
+ "keccak": "^3.0.0",
+ "pbkdf2": "^3.0.17",
+ "randombytes": "^2.1.0",
+ "safe-buffer": "^5.1.2",
+ "scrypt-js": "^3.0.0",
+ "secp256k1": "^4.0.1",
+ "setimmediate": "^1.0.5"
+ }
+ },
+ "node_modules/ethereumjs-abi": {
+ "version": "0.6.8",
+ "resolved": "https://registry.npmjs.org/ethereumjs-abi/-/ethereumjs-abi-0.6.8.tgz",
+ "integrity": "sha512-Tx0r/iXI6r+lRsdvkFDlut0N08jWMnKRZ6Gkq+Nmw75lZe4e6o3EkSnkaBP5NF6+m5PTGAr9JP43N3LyeoglsA==",
+ "dev": true,
+ "dependencies": {
+ "bn.js": "^4.11.8",
+ "ethereumjs-util": "^6.0.0"
+ }
+ },
+ "node_modules/ethereumjs-abi/node_modules/@types/bn.js": {
+ "version": "4.11.6",
+ "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz",
+ "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/ethereumjs-abi/node_modules/bn.js": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
+ "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
+ "dev": true
+ },
+ "node_modules/ethereumjs-abi/node_modules/ethereumjs-util": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz",
+ "integrity": "sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==",
+ "dev": true,
+ "dependencies": {
+ "@types/bn.js": "^4.11.3",
+ "bn.js": "^4.11.0",
+ "create-hash": "^1.1.2",
+ "elliptic": "^6.5.2",
+ "ethereum-cryptography": "^0.1.3",
+ "ethjs-util": "0.1.6",
+ "rlp": "^2.2.3"
+ }
+ },
+ "node_modules/ethereumjs-util": {
+ "version": "7.1.5",
+ "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz",
+ "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==",
+ "dev": true,
+ "dependencies": {
+ "@types/bn.js": "^5.1.0",
+ "bn.js": "^5.1.2",
+ "create-hash": "^1.1.2",
+ "ethereum-cryptography": "^0.1.3",
+ "rlp": "^2.2.4"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/ethers": {
+ "version": "5.7.2",
+ "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz",
+ "integrity": "sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "dependencies": {
+ "@ethersproject/abi": "5.7.0",
+ "@ethersproject/abstract-provider": "5.7.0",
+ "@ethersproject/abstract-signer": "5.7.0",
+ "@ethersproject/address": "5.7.0",
+ "@ethersproject/base64": "5.7.0",
+ "@ethersproject/basex": "5.7.0",
+ "@ethersproject/bignumber": "5.7.0",
+ "@ethersproject/bytes": "5.7.0",
+ "@ethersproject/constants": "5.7.0",
+ "@ethersproject/contracts": "5.7.0",
+ "@ethersproject/hash": "5.7.0",
+ "@ethersproject/hdnode": "5.7.0",
+ "@ethersproject/json-wallets": "5.7.0",
+ "@ethersproject/keccak256": "5.7.0",
+ "@ethersproject/logger": "5.7.0",
+ "@ethersproject/networks": "5.7.1",
+ "@ethersproject/pbkdf2": "5.7.0",
+ "@ethersproject/properties": "5.7.0",
+ "@ethersproject/providers": "5.7.2",
+ "@ethersproject/random": "5.7.0",
+ "@ethersproject/rlp": "5.7.0",
+ "@ethersproject/sha2": "5.7.0",
+ "@ethersproject/signing-key": "5.7.0",
+ "@ethersproject/solidity": "5.7.0",
+ "@ethersproject/strings": "5.7.0",
+ "@ethersproject/transactions": "5.7.0",
+ "@ethersproject/units": "5.7.0",
+ "@ethersproject/wallet": "5.7.0",
+ "@ethersproject/web": "5.7.1",
+ "@ethersproject/wordlists": "5.7.0"
+ }
+ },
+ "node_modules/ethjs-unit": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/ethjs-unit/-/ethjs-unit-0.1.6.tgz",
+ "integrity": "sha512-/Sn9Y0oKl0uqQuvgFk/zQgR7aw1g36qX/jzSQ5lSwlO0GigPymk4eGQfeNTD03w1dPOqfz8V77Cy43jH56pagw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "bn.js": "4.11.6",
+ "number-to-bn": "1.7.0"
+ },
+ "engines": {
+ "node": ">=6.5.0",
+ "npm": ">=3"
+ }
+ },
+ "node_modules/ethjs-unit/node_modules/bn.js": {
+ "version": "4.11.6",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz",
+ "integrity": "sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/ethjs-util": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.6.tgz",
+ "integrity": "sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==",
+ "dev": true,
+ "dependencies": {
+ "is-hex-prefixed": "1.0.0",
+ "strip-hex-prefix": "1.0.0"
+ },
+ "engines": {
+ "node": ">=6.5.0",
+ "npm": ">=3"
+ }
+ },
+ "node_modules/event-target-shim": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
+ "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/events": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
+ "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.x"
+ }
+ },
+ "node_modules/evp_bytestokey": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
+ "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
+ "dev": true,
+ "dependencies": {
+ "md5.js": "^1.3.4",
+ "safe-buffer": "^5.1.1"
+ }
+ },
+ "node_modules/extend": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/extsprintf": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
+ "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==",
+ "dev": true,
+ "engines": [
+ "node >=0.6.0"
+ ],
+ "peer": true
+ },
+ "node_modules/fast-base64-decode": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fast-base64-decode/-/fast-base64-decode-1.0.0.tgz",
+ "integrity": "sha512-qwaScUgUGBYeDNRnbc/KyllVU88Jk1pRHPStuF/lO7B0/RTRLj7U0lkdTAutlBblY08rwZDff6tNU9cjv6j//Q==",
+ "dev": true
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true
+ },
+ "node_modules/fast-diff": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
+ "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
+ "dev": true
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz",
+ "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true
+ },
+ "node_modules/fastq": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
+ "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==",
+ "dev": true,
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "dev": true,
+ "dependencies": {
+ "flat-cache": "^3.0.4"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-replace": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz",
+ "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "array-back": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/find-value": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/find-value/-/find-value-1.0.12.tgz",
+ "integrity": "sha512-OCpo8LTk8eZ2sdDCwbU2Lc3ivYsdM6yod6jP2jHcNEFcjPhkgH0+POzTIol7xx1LZgtbI5rkO5jqxsG5MWtPjQ==",
+ "dev": true
+ },
+ "node_modules/flat": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
+ "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
+ "dev": true,
+ "bin": {
+ "flat": "cli.js"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
+ "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
+ "dev": true,
+ "dependencies": {
+ "flatted": "^3.1.0",
+ "rimraf": "^3.0.2"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz",
+ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
+ "dev": true
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.15.2",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
+ "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/for-each": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
+ "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "is-callable": "^1.1.3"
+ }
+ },
+ "node_modules/forever-agent": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+ "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "dev": true,
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fp-ts": {
+ "version": "1.19.3",
+ "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-1.19.3.tgz",
+ "integrity": "sha512-H5KQDspykdHuztLTg+ajGN0Z2qUjcEf3Ybxc6hLt0k7/zPkn29XnKnxlBPyW2XIddWrGaJBzBl4VLYOtk39yZg==",
+ "dev": true
+ },
+ "node_modules/fs-extra": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
+ "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.1.2",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=6 <7 || >=8"
+ }
+ },
+ "node_modules/fs-readdir-recursive": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz",
+ "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/function.prototype.name": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz",
+ "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.19.0",
+ "functions-have-names": "^1.2.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/functional-red-black-tree": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
+ "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==",
+ "dev": true
+ },
+ "node_modules/functions-have-names": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
+ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
+ "dev": true,
+ "peer": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gauge": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/gauge/-/gauge-5.0.1.tgz",
+ "integrity": "sha512-CmykPMJGuNan/3S4kZOpvvPYSNqSHANiWnh9XcMU2pSjtBfF0XzZ2p1bFAxTbnFxyBuPxQYHhzwaoOmUdqzvxQ==",
+ "dev": true,
+ "dependencies": {
+ "aproba": "^1.0.3 || ^2.0.0",
+ "color-support": "^1.1.3",
+ "console-control-strings": "^1.1.0",
+ "has-unicode": "^2.0.1",
+ "signal-exit": "^4.0.1",
+ "string-width": "^4.2.3",
+ "strip-ansi": "^6.0.1",
+ "wide-align": "^1.1.5"
+ },
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
+ "node_modules/gauge/node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/gauge/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true,
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/get-func-name": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
+ "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
+ "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3",
+ "has-proto": "^1.0.1",
+ "has-symbols": "^1.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-port": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz",
+ "integrity": "sha512-x5UJKlgeUiNT8nyo/AcnwLnZuZNcSjSw0kogRB+Whd1fjjFq4B1hySFxSFWWSn4mIBzg3sRNUDFYc4g5gjPoLg==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/get-symbol-description": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz",
+ "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/getpass": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+ "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "assert-plus": "^1.0.0"
+ }
+ },
+ "node_modules/ghost-testrpc": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/ghost-testrpc/-/ghost-testrpc-0.0.2.tgz",
+ "integrity": "sha512-i08dAEgJ2g8z5buJIrCTduwPIhih3DP+hOCTyyryikfV8T0bNvHnGXO67i0DD1H4GBDETTclPy9njZbfluQYrQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "chalk": "^2.4.2",
+ "node-emoji": "^1.10.0"
+ },
+ "bin": {
+ "testrpc-sc": "index.js"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
+ "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/global-modules": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz",
+ "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "global-prefix": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/global-prefix": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz",
+ "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "ini": "^1.3.5",
+ "kind-of": "^6.0.2",
+ "which": "^1.3.1"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/global-prefix/node_modules/which": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+ "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "which": "bin/which"
+ }
+ },
+ "node_modules/globals": {
+ "version": "13.20.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz",
+ "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==",
+ "dev": true,
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/globalthis": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz",
+ "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "define-properties": "^1.1.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/globby": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
+ "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
+ "dev": true,
+ "dependencies": {
+ "array-union": "^2.1.0",
+ "dir-glob": "^3.0.1",
+ "fast-glob": "^3.2.9",
+ "ignore": "^5.2.0",
+ "merge2": "^1.4.1",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
+ "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "get-intrinsic": "^1.1.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true
+ },
+ "node_modules/graphemer": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+ "dev": true
+ },
+ "node_modules/growl": {
+ "version": "1.10.5",
+ "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
+ "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=4.x"
+ }
+ },
+ "node_modules/handlebars": {
+ "version": "4.7.7",
+ "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz",
+ "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "minimist": "^1.2.5",
+ "neo-async": "^2.6.0",
+ "source-map": "^0.6.1",
+ "wordwrap": "^1.0.0"
+ },
+ "bin": {
+ "handlebars": "bin/handlebars"
+ },
+ "engines": {
+ "node": ">=0.4.7"
+ },
+ "optionalDependencies": {
+ "uglify-js": "^3.1.4"
+ }
+ },
+ "node_modules/handlebars/node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/har-schema": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
+ "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/har-validator": {
+ "version": "5.1.5",
+ "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz",
+ "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==",
+ "deprecated": "this library is no longer supported",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "ajv": "^6.12.3",
+ "har-schema": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/hardhat": {
+ "version": "2.17.0",
+ "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.17.0.tgz",
+ "integrity": "sha512-CaEGa13tkJNe2/rdaBiive4pmdNShwxvdWVhr1zfb6aVpRhQt9VNO0l/UIBt/zzajz38ZFjvhfM2bj8LDXo9gw==",
+ "dev": true,
+ "dependencies": {
+ "@ethersproject/abi": "^5.1.2",
+ "@metamask/eth-sig-util": "^4.0.0",
+ "@nomicfoundation/ethereumjs-block": "5.0.1",
+ "@nomicfoundation/ethereumjs-blockchain": "7.0.1",
+ "@nomicfoundation/ethereumjs-common": "4.0.1",
+ "@nomicfoundation/ethereumjs-evm": "2.0.1",
+ "@nomicfoundation/ethereumjs-rlp": "5.0.1",
+ "@nomicfoundation/ethereumjs-statemanager": "2.0.1",
+ "@nomicfoundation/ethereumjs-trie": "6.0.1",
+ "@nomicfoundation/ethereumjs-tx": "5.0.1",
+ "@nomicfoundation/ethereumjs-util": "9.0.1",
+ "@nomicfoundation/ethereumjs-vm": "7.0.1",
+ "@nomicfoundation/solidity-analyzer": "^0.1.0",
+ "@sentry/node": "^5.18.1",
+ "@types/bn.js": "^5.1.0",
+ "@types/lru-cache": "^5.1.0",
+ "abort-controller": "^3.0.0",
+ "adm-zip": "^0.4.16",
+ "aggregate-error": "^3.0.0",
+ "ansi-escapes": "^4.3.0",
+ "chalk": "^2.4.2",
+ "chokidar": "^3.4.0",
+ "ci-info": "^2.0.0",
+ "debug": "^4.1.1",
+ "enquirer": "^2.3.0",
+ "env-paths": "^2.2.0",
+ "ethereum-cryptography": "^1.0.3",
+ "ethereumjs-abi": "^0.6.8",
+ "find-up": "^2.1.0",
+ "fp-ts": "1.19.3",
+ "fs-extra": "^7.0.1",
+ "glob": "7.2.0",
+ "immutable": "^4.0.0-rc.12",
+ "io-ts": "1.10.4",
+ "keccak": "^3.0.2",
+ "lodash": "^4.17.11",
+ "mnemonist": "^0.38.0",
+ "mocha": "^10.0.0",
+ "p-map": "^4.0.0",
+ "raw-body": "^2.4.1",
+ "resolve": "1.17.0",
+ "semver": "^6.3.0",
+ "solc": "0.7.3",
+ "source-map-support": "^0.5.13",
+ "stacktrace-parser": "^0.1.10",
+ "tsort": "0.0.1",
+ "undici": "^5.14.0",
+ "uuid": "^8.3.2",
+ "ws": "^7.4.6"
+ },
+ "bin": {
+ "hardhat": "internal/cli/bootstrap.js"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ },
+ "peerDependencies": {
+ "ts-node": "*",
+ "typescript": "*"
+ },
+ "peerDependenciesMeta": {
+ "ts-node": {
+ "optional": true
+ },
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/hardhat-gas-reporter": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/hardhat-gas-reporter/-/hardhat-gas-reporter-1.0.9.tgz",
+ "integrity": "sha512-INN26G3EW43adGKBNzYWOlI3+rlLnasXTwW79YNnUhXPDa+yHESgt639dJEs37gCjhkbNKcRRJnomXEuMFBXJg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "array-uniq": "1.0.3",
+ "eth-gas-reporter": "^0.2.25",
+ "sha1": "^1.1.1"
+ },
+ "peerDependencies": {
+ "hardhat": "^2.0.2"
+ }
+ },
+ "node_modules/hardhat-tracer": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/hardhat-tracer/-/hardhat-tracer-2.5.1.tgz",
+ "integrity": "sha512-0IDvoSyOCD+Lq+Vbq8fwHvhRDRodr74QpRAwU6V9RoJgiH4ohpqRpSlz2IhtPQcKSjDFsqOD55PyHCgZ578aUw==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^4.1.2",
+ "debug": "^4.3.4",
+ "ethers": "^5.6.1"
+ },
+ "peerDependencies": {
+ "chai": "4.x",
+ "hardhat": ">=2.16 <3.x"
+ }
+ },
+ "node_modules/hardhat-tracer/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/hardhat-tracer/node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/hardhat-tracer/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/hardhat-tracer/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/hardhat-tracer/node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/hardhat-tracer/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/hardhat/node_modules/ethereum-cryptography": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-1.2.0.tgz",
+ "integrity": "sha512-6yFQC9b5ug6/17CQpCyE3k9eKBMdhyVjzUy1WkiuY/E4vj/SXDBbCw8QEIaXqf0Mf2SnY6RmpDcwlUmBSS0EJw==",
+ "dev": true,
+ "dependencies": {
+ "@noble/hashes": "1.2.0",
+ "@noble/secp256k1": "1.7.1",
+ "@scure/bip32": "1.1.5",
+ "@scure/bip39": "1.1.1"
+ }
+ },
+ "node_modules/hardhat/node_modules/find-up": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
+ "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/hardhat/node_modules/locate-path": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
+ "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^2.0.0",
+ "path-exists": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/hardhat/node_modules/p-limit": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
+ "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
+ "dev": true,
+ "dependencies": {
+ "p-try": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/hardhat/node_modules/p-locate": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
+ "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/hardhat/node_modules/p-try": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
+ "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/hardhat/node_modules/path-exists": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+ "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "function-bind": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/has-bigints": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
+ "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==",
+ "dev": true,
+ "peer": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/has-property-descriptors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz",
+ "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "get-intrinsic": "^1.1.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
+ "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+ "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
+ "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "has-symbols": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-unicode": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
+ "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==",
+ "dev": true
+ },
+ "node_modules/hash-base": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz",
+ "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==",
+ "dev": true,
+ "dependencies": {
+ "inherits": "^2.0.4",
+ "readable-stream": "^3.6.0",
+ "safe-buffer": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/hash.js": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
+ "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
+ "dev": true,
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "minimalistic-assert": "^1.0.1"
+ }
+ },
+ "node_modules/he": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+ "dev": true,
+ "bin": {
+ "he": "bin/he"
+ }
+ },
+ "node_modules/heap": {
+ "version": "0.2.7",
+ "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz",
+ "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/hmac-drbg": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
+ "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==",
+ "dev": true,
+ "dependencies": {
+ "hash.js": "^1.0.3",
+ "minimalistic-assert": "^1.0.0",
+ "minimalistic-crypto-utils": "^1.0.1"
+ }
+ },
+ "node_modules/http-basic": {
+ "version": "8.1.3",
+ "resolved": "https://registry.npmjs.org/http-basic/-/http-basic-8.1.3.tgz",
+ "integrity": "sha512-/EcDMwJZh3mABI2NhGfHOGOeOZITqfkEO4p/xK+l3NpyncIHUQBoMvCSF/b5GqvKtySC2srL/GGG3+EtlqlmCw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "caseless": "^0.12.0",
+ "concat-stream": "^1.6.2",
+ "http-response-object": "^3.0.1",
+ "parse-cache-control": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+ "dev": true,
+ "dependencies": {
+ "depd": "2.0.0",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "toidentifier": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/http-response-object": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz",
+ "integrity": "sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@types/node": "^10.0.3"
+ }
+ },
+ "node_modules/http-response-object/node_modules/@types/node": {
+ "version": "10.17.60",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz",
+ "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/http-signature": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
+ "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "assert-plus": "^1.0.0",
+ "jsprim": "^1.2.2",
+ "sshpk": "^1.7.0"
+ },
+ "engines": {
+ "node": ">=0.8",
+ "npm": ">=1.3.7"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
+ "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
+ "dev": true,
+ "dependencies": {
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "dev": true,
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/ignore": {
+ "version": "5.2.4",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
+ "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/immutable": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.1.tgz",
+ "integrity": "sha512-lj9cnmB/kVS0QHsJnYKD1uo3o39nrbKxszjnqS9Fr6NB7bZzW45U6WSGBPKXDL/CvDKqDNPA4r3DoDQ8GTxo2A==",
+ "dev": true
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+ "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+ "dev": true,
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/indent-string": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
+ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "dev": true,
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
+ },
+ "node_modules/ini": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/internal-slot": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz",
+ "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "get-intrinsic": "^1.2.0",
+ "has": "^1.0.3",
+ "side-channel": "^1.0.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/interpret": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz",
+ "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/io-ts": {
+ "version": "1.10.4",
+ "resolved": "https://registry.npmjs.org/io-ts/-/io-ts-1.10.4.tgz",
+ "integrity": "sha512-b23PteSnYXSONJ6JQXRAlvJhuw8KOtkqa87W4wDtvMrud/DTJd5X+NpOOI+O/zZwVq6v0VLAaJ+1EDViKEuN9g==",
+ "dev": true,
+ "dependencies": {
+ "fp-ts": "^1.0.0"
+ }
+ },
+ "node_modules/is-array-buffer": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz",
+ "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.2.0",
+ "is-typed-array": "^1.1.10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "dev": true
+ },
+ "node_modules/is-bigint": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
+ "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "has-bigints": "^1.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-boolean-object": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
+ "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-buffer": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz",
+ "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/is-callable": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
+ "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-date-object": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
+ "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+ "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-hex-prefixed": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz",
+ "integrity": "sha512-WvtOiug1VFrE9v1Cydwm+FnXd3+w9GaeVUss5W4v/SLy3UW00vP+6iNF2SdnfiBoLy4bTqVdkftNGTUeOFVsbA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.5.0",
+ "npm": ">=3"
+ }
+ },
+ "node_modules/is-negative-zero": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
+ "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-number-object": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz",
+ "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-path-inside": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-plain-obj": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
+ "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-plain-object": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
+ "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
+ "dev": true,
+ "dependencies": {
+ "isobject": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-primitive": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-3.0.1.tgz",
+ "integrity": "sha512-GljRxhWvlCNRfZyORiH77FwdFwGcMO620o37EOYC0ORWdq+WYNVqW0w2Juzew4M+L81l6/QS3t5gkkihyRqv9w==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-regex": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
+ "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-shared-array-buffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
+ "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-string": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
+ "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-symbol": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz",
+ "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "has-symbols": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-typed-array": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz",
+ "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "which-typed-array": "^1.1.11"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-typedarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+ "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/is-unicode-supported": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
+ "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-weakref": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
+ "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
+ "dev": true
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true
+ },
+ "node_modules/isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/isomorphic-unfetch": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz",
+ "integrity": "sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q==",
+ "dev": true,
+ "dependencies": {
+ "node-fetch": "^2.6.1",
+ "unfetch": "^4.2.0"
+ }
+ },
+ "node_modules/isstream": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+ "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/iterate-object": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/iterate-object/-/iterate-object-1.3.4.tgz",
+ "integrity": "sha512-4dG1D1x/7g8PwHS9aK6QV5V94+ZvyP4+d19qDv43EzImmrndysIl4prmJ1hWWIGCqrZHyaHBm6BSEWHOLnpoNw==",
+ "dev": true
+ },
+ "node_modules/js-cookie": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz",
+ "integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==",
+ "dev": true
+ },
+ "node_modules/js-sdsl": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.2.tgz",
+ "integrity": "sha512-dwXFwByc/ajSV6m5bcKAPwe4yDDF6D614pxmIi5odytzxRlwqF6nwoiCek80Ixc7Cvma5awClxrzFtxCQvcM8w==",
+ "dev": true,
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/js-sdsl"
+ }
+ },
+ "node_modules/js-sha3": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz",
+ "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==",
+ "dev": true
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsbn": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+ "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "dev": true
+ },
+ "node_modules/json-schema": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
+ "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true
+ },
+ "node_modules/json-stringify-safe": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+ "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/jsonfile": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+ "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
+ "dev": true,
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/jsonschema": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.4.1.tgz",
+ "integrity": "sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/jsprim": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
+ "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "assert-plus": "1.0.0",
+ "extsprintf": "1.3.0",
+ "json-schema": "0.4.0",
+ "verror": "1.10.0"
+ },
+ "engines": {
+ "node": ">=0.6.0"
+ }
+ },
+ "node_modules/keccak": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.3.tgz",
+ "integrity": "sha512-JZrLIAJWuZxKbCilMpNz5Vj7Vtb4scDG3dMXLOsbzBmQGyjwE61BbW7bJkfKKCShXiQZt3T6sBgALRtmd+nZaQ==",
+ "dev": true,
+ "hasInstallScript": true,
+ "dependencies": {
+ "node-addon-api": "^2.0.0",
+ "node-gyp-build": "^4.2.0",
+ "readable-stream": "^3.6.0"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/kind-of": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/klaw": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz",
+ "integrity": "sha512-TED5xi9gGQjGpNnvRWknrwAB1eL5GciPfVFOt3Vk1OJCVDQbzuSfrF3hkUQKlsgKrG1F+0t5W0m+Fje1jIt8rw==",
+ "dev": true,
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.9"
+ }
+ },
+ "node_modules/level": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/level/-/level-8.0.0.tgz",
+ "integrity": "sha512-ypf0jjAk2BWI33yzEaaotpq7fkOPALKAgDBxggO6Q9HGX2MRXn0wbP1Jn/tJv1gtL867+YOjOB49WaUF3UoJNQ==",
+ "dev": true,
+ "dependencies": {
+ "browser-level": "^1.0.1",
+ "classic-level": "^1.2.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/level"
+ }
+ },
+ "node_modules/level-supports": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-4.0.1.tgz",
+ "integrity": "sha512-PbXpve8rKeNcZ9C1mUicC9auIYFyGpkV9/i6g76tLgANwWhtG2v7I4xNBUlkn3lE2/dZF3Pi0ygYGtLc4RXXdA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/level-transcoder": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/level-transcoder/-/level-transcoder-1.0.1.tgz",
+ "integrity": "sha512-t7bFwFtsQeD8cl8NIoQ2iwxA0CL/9IFw7/9gAjOonH0PWTTiRfY7Hq+Ejbsxh86tXobDQ6IOiddjNYIfOBs06w==",
+ "dev": true,
+ "dependencies": {
+ "buffer": "^6.0.3",
+ "module-error": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/level-transcoder/node_modules/buffer": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
+ "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.2.1"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "dev": true
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "dev": true
+ },
+ "node_modules/lodash.camelcase": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
+ "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true
+ },
+ "node_modules/lodash.truncate": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz",
+ "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==",
+ "dev": true
+ },
+ "node_modules/log-symbols": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
+ "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^4.1.0",
+ "is-unicode-supported": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/log-symbols/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/log-symbols/node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/log-symbols/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/log-symbols/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/log-symbols/node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/log-symbols/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/loupe": {
+ "version": "2.3.6",
+ "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz",
+ "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "get-func-name": "^2.0.0"
+ }
+ },
+ "node_modules/lru_map": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz",
+ "integrity": "sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==",
+ "dev": true
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/make-error": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
+ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/markdown-table": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-1.1.3.tgz",
+ "integrity": "sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/mcl-wasm": {
+ "version": "0.7.9",
+ "resolved": "https://registry.npmjs.org/mcl-wasm/-/mcl-wasm-0.7.9.tgz",
+ "integrity": "sha512-iJIUcQWA88IJB/5L15GnJVnSQJmf/YaxxV6zRavv83HILHaJQb6y0iFyDMdDO0gN8X37tdxmAOrH/P8B6RB8sQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8.9.0"
+ }
+ },
+ "node_modules/md5.js": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
+ "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
+ "dev": true,
+ "dependencies": {
+ "hash-base": "^3.0.0",
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.1.2"
+ }
+ },
+ "node_modules/memory-level": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/memory-level/-/memory-level-1.0.0.tgz",
+ "integrity": "sha512-UXzwewuWeHBz5krr7EvehKcmLFNoXxGcvuYhC41tRnkrTbJohtS7kVn9akmgirtRygg+f7Yjsfi8Uu5SGSQ4Og==",
+ "dev": true,
+ "dependencies": {
+ "abstract-level": "^1.0.0",
+ "functional-red-black-tree": "^1.0.1",
+ "module-error": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/memorystream": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz",
+ "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.10.0"
+ }
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
+ "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
+ "dev": true,
+ "dependencies": {
+ "braces": "^3.0.2",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dev": true,
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/minimalistic-assert": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
+ "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
+ "dev": true
+ },
+ "node_modules/minimalistic-crypto-utils": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
+ "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==",
+ "dev": true
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/mkdirp": {
+ "version": "0.5.6",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
+ "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "minimist": "^1.2.6"
+ },
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ }
+ },
+ "node_modules/mnemonist": {
+ "version": "0.38.5",
+ "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.38.5.tgz",
+ "integrity": "sha512-bZTFT5rrPKtPJxj8KSV0WkPyNxl72vQepqqVUAW2ARUpUSF2qXMB6jZj7hW5/k7C1rtpzqbD/IIbJwLXUjCHeg==",
+ "dev": true,
+ "dependencies": {
+ "obliterator": "^2.0.0"
+ }
+ },
+ "node_modules/mocha": {
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz",
+ "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==",
+ "dev": true,
+ "dependencies": {
+ "ansi-colors": "4.1.1",
+ "browser-stdout": "1.3.1",
+ "chokidar": "3.5.3",
+ "debug": "4.3.4",
+ "diff": "5.0.0",
+ "escape-string-regexp": "4.0.0",
+ "find-up": "5.0.0",
+ "glob": "7.2.0",
+ "he": "1.2.0",
+ "js-yaml": "4.1.0",
+ "log-symbols": "4.1.0",
+ "minimatch": "5.0.1",
+ "ms": "2.1.3",
+ "nanoid": "3.3.3",
+ "serialize-javascript": "6.0.0",
+ "strip-json-comments": "3.1.1",
+ "supports-color": "8.1.1",
+ "workerpool": "6.2.1",
+ "yargs": "16.2.0",
+ "yargs-parser": "20.2.4",
+ "yargs-unparser": "2.0.0"
+ },
+ "bin": {
+ "_mocha": "bin/_mocha",
+ "mocha": "bin/mocha.js"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mochajs"
+ }
+ },
+ "node_modules/mocha/node_modules/ansi-colors": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
+ "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/mocha/node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/mocha/node_modules/cliui": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
+ "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
+ "dev": true,
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^7.0.0"
+ }
+ },
+ "node_modules/mocha/node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/mocha/node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/mocha/node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/mocha/node_modules/minimatch": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz",
+ "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/mocha/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true
+ },
+ "node_modules/mocha/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/mocha/node_modules/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "node_modules/mocha/node_modules/yargs": {
+ "version": "16.2.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
+ "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
+ "dev": true,
+ "dependencies": {
+ "cliui": "^7.0.2",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.0",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^20.2.2"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/module-error": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/module-error/-/module-error-1.0.2.tgz",
+ "integrity": "sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz",
+ "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==",
+ "dev": true,
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/napi-macros": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.2.2.tgz",
+ "integrity": "sha512-hmEVtAGYzVQpCKdbQea4skABsdXW4RUh5t5mJ2zzqowJS2OyXZTU1KhDVFhx+NlWZ4ap9mqR9TcDO3LTTttd+g==",
+ "dev": true
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true
+ },
+ "node_modules/natural-compare-lite": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz",
+ "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==",
+ "dev": true
+ },
+ "node_modules/neo-async": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
+ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/node-addon-api": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz",
+ "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==",
+ "dev": true
+ },
+ "node_modules/node-emoji": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz",
+ "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "lodash": "^4.17.21"
+ }
+ },
+ "node_modules/node-environment-flags": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz",
+ "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "object.getownpropertydescriptors": "^2.0.3",
+ "semver": "^5.7.0"
+ }
+ },
+ "node_modules/node-environment-flags/node_modules/semver": {
+ "version": "5.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
+ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
+ "dev": true,
+ "peer": true,
+ "bin": {
+ "semver": "bin/semver"
+ }
+ },
+ "node_modules/node-fetch": {
+ "version": "2.6.12",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz",
+ "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==",
+ "dev": true,
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
+ "engines": {
+ "node": "4.x || >=6.0.0"
+ },
+ "peerDependencies": {
+ "encoding": "^0.1.0"
+ },
+ "peerDependenciesMeta": {
+ "encoding": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/node-gyp-build": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz",
+ "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==",
+ "dev": true,
+ "bin": {
+ "node-gyp-build": "bin.js",
+ "node-gyp-build-optional": "optional.js",
+ "node-gyp-build-test": "build-test.js"
+ }
+ },
+ "node_modules/nofilter": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz",
+ "integrity": "sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==",
+ "dev": true,
+ "engines": {
+ "node": ">=12.19"
+ }
+ },
+ "node_modules/nopt": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
+ "integrity": "sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "abbrev": "1"
+ },
+ "bin": {
+ "nopt": "bin/nopt.js"
+ }
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npmlog": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-7.0.1.tgz",
+ "integrity": "sha512-uJ0YFk/mCQpLBt+bxN88AKd+gyqZvZDbtiNxk6Waqcj2aPRyfVx8ITawkyQynxUagInjdYT1+qj4NfA5KJJUxg==",
+ "dev": true,
+ "dependencies": {
+ "are-we-there-yet": "^4.0.0",
+ "console-control-strings": "^1.1.0",
+ "gauge": "^5.0.0",
+ "set-blocking": "^2.0.0"
+ },
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
+ "node_modules/number-to-bn": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/number-to-bn/-/number-to-bn-1.7.0.tgz",
+ "integrity": "sha512-wsJ9gfSz1/s4ZsJN01lyonwuxA1tml6X1yBDnfpMglypcBRFZZkus26EdPSlqS5GJfYddVZa22p3VNb3z5m5Ig==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "bn.js": "4.11.6",
+ "strip-hex-prefix": "1.0.0"
+ },
+ "engines": {
+ "node": ">=6.5.0",
+ "npm": ">=3"
+ }
+ },
+ "node_modules/number-to-bn/node_modules/bn.js": {
+ "version": "4.11.6",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz",
+ "integrity": "sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/oauth-sign": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
+ "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.12.3",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
+ "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
+ "dev": true,
+ "peer": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object-keys": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.assign": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz",
+ "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "define-properties": "^1.1.2",
+ "function-bind": "^1.1.1",
+ "has-symbols": "^1.0.0",
+ "object-keys": "^1.0.11"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.getownpropertydescriptors": {
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.6.tgz",
+ "integrity": "sha512-lq+61g26E/BgHv0ZTFgRvi7NMEPuAxLkFU7rukXjc/AlwH4Am5xXVnIXy3un1bg/JPbXHrixRkK1itUzzPiIjQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "array.prototype.reduce": "^1.0.5",
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.21.2",
+ "safe-array-concat": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/obliterator": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.4.tgz",
+ "integrity": "sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==",
+ "dev": true
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.3",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
+ "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==",
+ "dev": true,
+ "dependencies": {
+ "@aashutoshrathi/word-wrap": "^1.2.3",
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/ordinal": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/ordinal/-/ordinal-1.0.3.tgz",
+ "integrity": "sha512-cMddMgb2QElm8G7vdaa02jhUNbTSrhsgAGUz1OokD83uJTwSUn+nKoNoKVVaRa08yF6sgfO7Maou1+bgLd9rdQ==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/os-tmpdir": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+ "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-map": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
+ "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
+ "dev": true,
+ "dependencies": {
+ "aggregate-error": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse-cache-control": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz",
+ "integrity": "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true
+ },
+ "node_modules/path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pathval": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz",
+ "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/pbkdf2": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz",
+ "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==",
+ "dev": true,
+ "dependencies": {
+ "create-hash": "^1.1.2",
+ "create-hmac": "^1.1.4",
+ "ripemd160": "^2.0.1",
+ "safe-buffer": "^5.0.1",
+ "sha.js": "^2.4.8"
+ },
+ "engines": {
+ "node": ">=0.12"
+ }
+ },
+ "node_modules/performance-now": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+ "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pify": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+ "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/pluralize": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz",
+ "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/prettier": {
+ "version": "2.8.8",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
+ "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
+ "dev": true,
+ "bin": {
+ "prettier": "bin-prettier.js"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/prettier-linter-helpers": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
+ "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
+ "dev": true,
+ "dependencies": {
+ "fast-diff": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/prettier-plugin-solidity": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/prettier-plugin-solidity/-/prettier-plugin-solidity-1.1.3.tgz",
+ "integrity": "sha512-fQ9yucPi2sBbA2U2Xjh6m4isUTJ7S7QLc/XDDsktqqxYfTwdYKJ0EnnywXHwCGAaYbQNK+HIYPL1OemxuMsgeg==",
+ "dev": true,
+ "dependencies": {
+ "@solidity-parser/parser": "^0.16.0",
+ "semver": "^7.3.8",
+ "solidity-comments-extractor": "^0.0.7"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "peerDependencies": {
+ "prettier": ">=2.3.0 || >=3.0.0-alpha.0"
+ }
+ },
+ "node_modules/prettier-plugin-solidity/node_modules/@solidity-parser/parser": {
+ "version": "0.16.1",
+ "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.16.1.tgz",
+ "integrity": "sha512-PdhRFNhbTtu3x8Axm0uYpqOy/lODYQK+MlYSgqIsq2L8SFYEHJPHNUiOTAJbDGzNjjr1/n9AcIayxafR/fWmYw==",
+ "dev": true,
+ "dependencies": {
+ "antlr4ts": "^0.5.0-alpha.4"
+ }
+ },
+ "node_modules/prettier-plugin-solidity/node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/prettier-plugin-solidity/node_modules/semver": {
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+ "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+ "dev": true,
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/prettier-plugin-solidity/node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
+ },
+ "node_modules/process": {
+ "version": "0.11.10",
+ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
+ "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.6.0"
+ }
+ },
+ "node_modules/process-nextick-args": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/promise": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz",
+ "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "asap": "~2.0.6"
+ }
+ },
+ "node_modules/proper-lockfile": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz",
+ "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "retry": "^0.12.0",
+ "signal-exit": "^3.0.2"
+ }
+ },
+ "node_modules/proper-lockfile/node_modules/retry": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
+ "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/proper-lockfile/node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "dev": true
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "dev": true
+ },
+ "node_modules/psl": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
+ "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/punycode": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
+ "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.5.3",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz",
+ "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/r-json": {
+ "version": "1.2.10",
+ "resolved": "https://registry.npmjs.org/r-json/-/r-json-1.2.10.tgz",
+ "integrity": "sha512-hu9vyLjSlHXT62NAS7DjI9WazDlvjN0lgp3n431dCVnirVcLkZIpzSwA3orhZEKzdDD2jqNYI+w0yG0aFf4kpA==",
+ "dev": true
+ },
+ "node_modules/randombytes": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+ "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+ "dev": true,
+ "dependencies": {
+ "safe-buffer": "^5.1.0"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
+ "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
+ "dev": true,
+ "dependencies": {
+ "bytes": "3.1.2",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "dev": true,
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/rechoir": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz",
+ "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "resolve": "^1.1.6"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/recursive-readdir": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz",
+ "integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "minimatch": "^3.0.5"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/reduce-flatten": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz",
+ "integrity": "sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/regexp.prototype.flags": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz",
+ "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "functions-have-names": "^1.2.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/req-cwd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/req-cwd/-/req-cwd-2.0.0.tgz",
+ "integrity": "sha512-ueoIoLo1OfB6b05COxAA9UpeoscNpYyM+BqYlA7H6LVF4hKGPXQQSSaD2YmvDVJMkk4UDpAHIeU1zG53IqjvlQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "req-from": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/req-from": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/req-from/-/req-from-2.0.0.tgz",
+ "integrity": "sha512-LzTfEVDVQHBRfjOUMgNBA+V6DWsSnoeKzf42J7l0xa/B4jyPOuuF5MlNSmomLNGemWTnV2TIdjSSLnEn95fOQA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "resolve-from": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/req-from/node_modules/resolve-from": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz",
+ "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/request": {
+ "version": "2.88.2",
+ "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
+ "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
+ "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "aws-sign2": "~0.7.0",
+ "aws4": "^1.8.0",
+ "caseless": "~0.12.0",
+ "combined-stream": "~1.0.6",
+ "extend": "~3.0.2",
+ "forever-agent": "~0.6.1",
+ "form-data": "~2.3.2",
+ "har-validator": "~5.1.3",
+ "http-signature": "~1.2.0",
+ "is-typedarray": "~1.0.0",
+ "isstream": "~0.1.2",
+ "json-stringify-safe": "~5.0.1",
+ "mime-types": "~2.1.19",
+ "oauth-sign": "~0.9.0",
+ "performance-now": "^2.1.0",
+ "qs": "~6.5.2",
+ "safe-buffer": "^5.1.2",
+ "tough-cookie": "~2.5.0",
+ "tunnel-agent": "^0.6.0",
+ "uuid": "^3.3.2"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/request-promise-core": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz",
+ "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "lodash": "^4.17.19"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "peerDependencies": {
+ "request": "^2.34"
+ }
+ },
+ "node_modules/request-promise-native": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz",
+ "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==",
+ "deprecated": "request-promise-native has been deprecated because it extends the now deprecated request package, see https://github.com/request/request/issues/3142",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "request-promise-core": "1.1.4",
+ "stealthy-require": "^1.1.1",
+ "tough-cookie": "^2.3.3"
+ },
+ "engines": {
+ "node": ">=0.12.0"
+ },
+ "peerDependencies": {
+ "request": "^2.34"
+ }
+ },
+ "node_modules/request/node_modules/form-data": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
+ "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.6",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 0.12"
+ }
+ },
+ "node_modules/request/node_modules/uuid": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
+ "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
+ "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.",
+ "dev": true,
+ "peer": true,
+ "bin": {
+ "uuid": "bin/uuid"
+ }
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/require-main-filename": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/resolve": {
+ "version": "1.17.0",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz",
+ "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==",
+ "dev": true,
+ "dependencies": {
+ "path-parse": "^1.0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/retry": {
+ "version": "0.13.1",
+ "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
+ "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+ "dev": true,
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "dev": true,
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/ripemd160": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
+ "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
+ "dev": true,
+ "dependencies": {
+ "hash-base": "^3.0.0",
+ "inherits": "^2.0.1"
+ }
+ },
+ "node_modules/rlp": {
+ "version": "2.2.7",
+ "resolved": "https://registry.npmjs.org/rlp/-/rlp-2.2.7.tgz",
+ "integrity": "sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ==",
+ "dev": true,
+ "dependencies": {
+ "bn.js": "^5.2.0"
+ },
+ "bin": {
+ "rlp": "bin/rlp"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/run-parallel-limit": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/run-parallel-limit/-/run-parallel-limit-1.1.0.tgz",
+ "integrity": "sha512-jJA7irRNM91jaKc3Hcl1npHsFLOXOoTkPCUL1JEa1R82O2miplXXRaGdjW/KM/98YQWDhJLiSs793CnXfblJUw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/rustbn.js": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/rustbn.js/-/rustbn.js-0.2.0.tgz",
+ "integrity": "sha512-4VlvkRUuCJvr2J6Y0ImW7NvTCriMi7ErOAqWk1y69vAdoNIzCF3yPmgeNzx+RQTLEDFq5sHfscn1MwHxP9hNfA==",
+ "dev": true
+ },
+ "node_modules/safe-array-concat": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.0.tgz",
+ "integrity": "sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.2.0",
+ "has-symbols": "^1.0.3",
+ "isarray": "^2.0.5"
+ },
+ "engines": {
+ "node": ">=0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/safe-array-concat/node_modules/isarray": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
+ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/safe-regex-test": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz",
+ "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.1.3",
+ "is-regex": "^1.1.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true
+ },
+ "node_modules/sc-istanbul": {
+ "version": "0.4.6",
+ "resolved": "https://registry.npmjs.org/sc-istanbul/-/sc-istanbul-0.4.6.tgz",
+ "integrity": "sha512-qJFF/8tW/zJsbyfh/iT/ZM5QNHE3CXxtLJbZsL+CzdJLBsPD7SedJZoUA4d8iAcN2IoMp/Dx80shOOd2x96X/g==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "abbrev": "1.0.x",
+ "async": "1.x",
+ "escodegen": "1.8.x",
+ "esprima": "2.7.x",
+ "glob": "^5.0.15",
+ "handlebars": "^4.0.1",
+ "js-yaml": "3.x",
+ "mkdirp": "0.5.x",
+ "nopt": "3.x",
+ "once": "1.x",
+ "resolve": "1.1.x",
+ "supports-color": "^3.1.0",
+ "which": "^1.1.1",
+ "wordwrap": "^1.0.0"
+ },
+ "bin": {
+ "istanbul": "lib/cli.js"
+ }
+ },
+ "node_modules/sc-istanbul/node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "node_modules/sc-istanbul/node_modules/glob": {
+ "version": "5.0.15",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz",
+ "integrity": "sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "2 || 3",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/sc-istanbul/node_modules/has-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz",
+ "integrity": "sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/sc-istanbul/node_modules/js-yaml": {
+ "version": "3.14.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
+ "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/sc-istanbul/node_modules/js-yaml/node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true,
+ "peer": true,
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/sc-istanbul/node_modules/resolve": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz",
+ "integrity": "sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/sc-istanbul/node_modules/supports-color": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz",
+ "integrity": "sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "has-flag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/sc-istanbul/node_modules/which": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+ "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "which": "bin/which"
+ }
+ },
+ "node_modules/scrypt-js": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz",
+ "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==",
+ "dev": true
+ },
+ "node_modules/secp256k1": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.3.tgz",
+ "integrity": "sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "dependencies": {
+ "elliptic": "^6.5.4",
+ "node-addon-api": "^2.0.0",
+ "node-gyp-build": "^4.2.0"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/serialize-javascript": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz",
+ "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==",
+ "dev": true,
+ "dependencies": {
+ "randombytes": "^2.1.0"
+ }
+ },
+ "node_modules/set-blocking": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
+ "dev": true
+ },
+ "node_modules/set-value": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/set-value/-/set-value-4.1.0.tgz",
+ "integrity": "sha512-zTEg4HL0RwVrqcWs3ztF+x1vkxfm0lP+MQQFPiMJTKVceBwEV0A569Ou8l9IYQG8jOZdMVI1hGsc0tmeD2o/Lw==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/jonschlinkert",
+ "https://paypal.me/jonathanschlinkert",
+ "https://jonschlinkert.dev/sponsor"
+ ],
+ "dependencies": {
+ "is-plain-object": "^2.0.4",
+ "is-primitive": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=11.0"
+ }
+ },
+ "node_modules/setimmediate": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
+ "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
+ "dev": true
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "dev": true
+ },
+ "node_modules/sha.js": {
+ "version": "2.4.11",
+ "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
+ "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
+ "dev": true,
+ "dependencies": {
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ },
+ "bin": {
+ "sha.js": "bin.js"
+ }
+ },
+ "node_modules/sha1": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/sha1/-/sha1-1.1.1.tgz",
+ "integrity": "sha512-dZBS6OrMjtgVkopB1Gmo4RQCDKiZsqcpAQpkV/aaj+FCrCg8r4I4qMkDPQjBgLIxlmu9k4nUbWq6ohXahOneYA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "charenc": ">= 0.0.1",
+ "crypt": ">= 0.0.1"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shelljs": {
+ "version": "0.8.5",
+ "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz",
+ "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "glob": "^7.0.0",
+ "interpret": "^1.0.0",
+ "rechoir": "^0.6.2"
+ },
+ "bin": {
+ "shjs": "bin/shjs"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/side-channel": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
+ "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.0",
+ "get-intrinsic": "^1.0.2",
+ "object-inspect": "^1.9.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.2.tgz",
+ "integrity": "sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/slash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/slice-ansi": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz",
+ "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "astral-regex": "^2.0.0",
+ "is-fullwidth-code-point": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/slice-ansi?sponsor=1"
+ }
+ },
+ "node_modules/slice-ansi/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/slice-ansi/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/slice-ansi/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/solc": {
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/solc/-/solc-0.7.3.tgz",
+ "integrity": "sha512-GAsWNAjGzIDg7VxzP6mPjdurby3IkGCjQcM8GFYZT6RyaoUZKmMU6Y7YwG+tFGhv7dwZ8rmR4iwFDrrD99JwqA==",
+ "dev": true,
+ "dependencies": {
+ "command-exists": "^1.2.8",
+ "commander": "3.0.2",
+ "follow-redirects": "^1.12.1",
+ "fs-extra": "^0.30.0",
+ "js-sha3": "0.8.0",
+ "memorystream": "^0.3.1",
+ "require-from-string": "^2.0.0",
+ "semver": "^5.5.0",
+ "tmp": "0.0.33"
+ },
+ "bin": {
+ "solcjs": "solcjs"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/solc/node_modules/fs-extra": {
+ "version": "0.30.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz",
+ "integrity": "sha512-UvSPKyhMn6LEd/WpUaV9C9t3zATuqoqfWc3QdPhPLb58prN9tqYPlPWi8Krxi44loBoUzlobqZ3+8tGpxxSzwA==",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.1.2",
+ "jsonfile": "^2.1.0",
+ "klaw": "^1.0.0",
+ "path-is-absolute": "^1.0.0",
+ "rimraf": "^2.2.8"
+ }
+ },
+ "node_modules/solc/node_modules/jsonfile": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz",
+ "integrity": "sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==",
+ "dev": true,
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/solc/node_modules/rimraf": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
+ "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
+ "dev": true,
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ }
+ },
+ "node_modules/solc/node_modules/semver": {
+ "version": "5.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
+ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver"
+ }
+ },
+ "node_modules/solhint": {
+ "version": "3.4.1",
+ "resolved": "https://registry.npmjs.org/solhint/-/solhint-3.4.1.tgz",
+ "integrity": "sha512-pzZn2RlZhws1XwvLPVSsxfHrwsteFf5eySOhpAytzXwKQYbTCJV6z8EevYDiSVKMpWrvbKpEtJ055CuEmzp4Xg==",
+ "dev": true,
+ "dependencies": {
+ "@solidity-parser/parser": "^0.16.0",
+ "ajv": "^6.12.6",
+ "antlr4": "^4.11.0",
+ "ast-parents": "^0.0.1",
+ "chalk": "^4.1.2",
+ "commander": "^10.0.0",
+ "cosmiconfig": "^8.0.0",
+ "fast-diff": "^1.2.0",
+ "glob": "^8.0.3",
+ "ignore": "^5.2.4",
+ "js-yaml": "^4.1.0",
+ "lodash": "^4.17.21",
+ "pluralize": "^8.0.0",
+ "semver": "^6.3.0",
+ "strip-ansi": "^6.0.1",
+ "table": "^6.8.1",
+ "text-table": "^0.2.0"
+ },
+ "bin": {
+ "solhint": "solhint.js"
+ },
+ "optionalDependencies": {
+ "prettier": "^2.8.3"
+ }
+ },
+ "node_modules/solhint-plugin-prettier": {
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/solhint-plugin-prettier/-/solhint-plugin-prettier-0.0.5.tgz",
+ "integrity": "sha512-7jmWcnVshIrO2FFinIvDQmhQpfpS2rRRn3RejiYgnjIE68xO2bvrYvjqVNfrio4xH9ghOqn83tKuTzLjEbmGIA==",
+ "dev": true,
+ "dependencies": {
+ "prettier-linter-helpers": "^1.0.0"
+ },
+ "peerDependencies": {
+ "prettier": "^1.15.0 || ^2.0.0",
+ "prettier-plugin-solidity": "^1.0.0-alpha.14"
+ }
+ },
+ "node_modules/solhint/node_modules/@solidity-parser/parser": {
+ "version": "0.16.1",
+ "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.16.1.tgz",
+ "integrity": "sha512-PdhRFNhbTtu3x8Axm0uYpqOy/lODYQK+MlYSgqIsq2L8SFYEHJPHNUiOTAJbDGzNjjr1/n9AcIayxafR/fWmYw==",
+ "dev": true,
+ "dependencies": {
+ "antlr4ts": "^0.5.0-alpha.4"
+ }
+ },
+ "node_modules/solhint/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/solhint/node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/solhint/node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/solhint/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/solhint/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/solhint/node_modules/commander": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
+ "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
+ "dev": true,
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/solhint/node_modules/glob": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
+ "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^5.0.1",
+ "once": "^1.3.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/solhint/node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/solhint/node_modules/minimatch": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
+ "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/solhint/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/solidity-ast": {
+ "version": "0.4.49",
+ "resolved": "https://registry.npmjs.org/solidity-ast/-/solidity-ast-0.4.49.tgz",
+ "integrity": "sha512-Pr5sCAj1SFqzwFZw1HPKSq0PehlQNdM8GwKyAVYh2DOn7/cCK8LUKD1HeHnKtTgBW7hi9h4nnnan7hpAg5RhWQ==",
+ "dev": true
+ },
+ "node_modules/solidity-comments-extractor": {
+ "version": "0.0.7",
+ "resolved": "https://registry.npmjs.org/solidity-comments-extractor/-/solidity-comments-extractor-0.0.7.tgz",
+ "integrity": "sha512-wciNMLg/Irp8OKGrh3S2tfvZiZ0NEyILfcRCXCD4mp7SgK/i9gzLfhY2hY7VMCQJ3kH9UB9BzNdibIVMchzyYw==",
+ "dev": true
+ },
+ "node_modules/solidity-coverage": {
+ "version": "0.8.4",
+ "resolved": "https://registry.npmjs.org/solidity-coverage/-/solidity-coverage-0.8.4.tgz",
+ "integrity": "sha512-xeHOfBOjdMF6hWTbt42iH4x+7j1Atmrf5OldDPMxI+i/COdExUxszOswD9qqvcBTaLGiOrrpnh9UZjSpt4rBsg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@ethersproject/abi": "^5.0.9",
+ "@solidity-parser/parser": "^0.16.0",
+ "chalk": "^2.4.2",
+ "death": "^1.1.0",
+ "detect-port": "^1.3.0",
+ "difflib": "^0.2.4",
+ "fs-extra": "^8.1.0",
+ "ghost-testrpc": "^0.0.2",
+ "global-modules": "^2.0.0",
+ "globby": "^10.0.1",
+ "jsonschema": "^1.2.4",
+ "lodash": "^4.17.15",
+ "mocha": "7.1.2",
+ "node-emoji": "^1.10.0",
+ "pify": "^4.0.1",
+ "recursive-readdir": "^2.2.2",
+ "sc-istanbul": "^0.4.5",
+ "semver": "^7.3.4",
+ "shelljs": "^0.8.3",
+ "web3-utils": "^1.3.6"
+ },
+ "bin": {
+ "solidity-coverage": "plugins/bin.js"
+ },
+ "peerDependencies": {
+ "hardhat": "^2.11.0"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/@solidity-parser/parser": {
+ "version": "0.16.1",
+ "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.16.1.tgz",
+ "integrity": "sha512-PdhRFNhbTtu3x8Axm0uYpqOy/lODYQK+MlYSgqIsq2L8SFYEHJPHNUiOTAJbDGzNjjr1/n9AcIayxafR/fWmYw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "antlr4ts": "^0.5.0-alpha.4"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/ansi-colors": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz",
+ "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/ansi-regex": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
+ "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/chokidar": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz",
+ "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "anymatch": "~3.1.1",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.0",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.2.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.1.1"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/cliui": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
+ "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "string-width": "^3.1.0",
+ "strip-ansi": "^5.2.0",
+ "wrap-ansi": "^5.1.0"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/cliui/node_modules/string-width": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "emoji-regex": "^7.0.1",
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/debug": {
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+ "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+ "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/decamelize": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+ "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/diff": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
+ "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/emoji-regex": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+ "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/solidity-coverage/node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true,
+ "peer": true,
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/find-up": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "locate-path": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/flat": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.1.tgz",
+ "integrity": "sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "is-buffer": "~2.0.3"
+ },
+ "bin": {
+ "flat": "cli.js"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/fs-extra": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
+ "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=6 <7 || >=8"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/fsevents": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz",
+ "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==",
+ "deprecated": "\"Please update to latest v2.3 or v2.2\"",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "peer": true,
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/glob": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
+ "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/globby": {
+ "version": "10.0.2",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.2.tgz",
+ "integrity": "sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@types/glob": "^7.1.1",
+ "array-union": "^2.1.0",
+ "dir-glob": "^3.0.1",
+ "fast-glob": "^3.0.3",
+ "glob": "^7.1.3",
+ "ignore": "^5.1.1",
+ "merge2": "^1.2.3",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/js-yaml": {
+ "version": "3.13.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
+ "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/locate-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "p-locate": "^3.0.0",
+ "path-exists": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/log-symbols": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz",
+ "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "chalk": "^2.4.2"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/minimatch": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/mkdirp": {
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
+ "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "minimist": "^1.2.5"
+ },
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/mocha": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.1.2.tgz",
+ "integrity": "sha512-o96kdRKMKI3E8U0bjnfqW4QMk12MwZ4mhdBTf+B5a1q9+aq2HRnj+3ZdJu0B/ZhJeK78MgYuv6L8d/rA5AeBJA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "ansi-colors": "3.2.3",
+ "browser-stdout": "1.3.1",
+ "chokidar": "3.3.0",
+ "debug": "3.2.6",
+ "diff": "3.5.0",
+ "escape-string-regexp": "1.0.5",
+ "find-up": "3.0.0",
+ "glob": "7.1.3",
+ "growl": "1.10.5",
+ "he": "1.2.0",
+ "js-yaml": "3.13.1",
+ "log-symbols": "3.0.0",
+ "minimatch": "3.0.4",
+ "mkdirp": "0.5.5",
+ "ms": "2.1.1",
+ "node-environment-flags": "1.0.6",
+ "object.assign": "4.1.0",
+ "strip-json-comments": "2.0.1",
+ "supports-color": "6.0.0",
+ "which": "1.3.1",
+ "wide-align": "1.1.3",
+ "yargs": "13.3.2",
+ "yargs-parser": "13.1.2",
+ "yargs-unparser": "1.6.0"
+ },
+ "bin": {
+ "_mocha": "bin/_mocha",
+ "mocha": "bin/mocha"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mochajs"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/ms": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+ "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/solidity-coverage/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/p-locate": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "p-limit": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/path-exists": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+ "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/readdirp": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz",
+ "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/semver": {
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+ "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/strip-ansi": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "ansi-regex": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/strip-json-comments": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+ "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/supports-color": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz",
+ "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/which": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+ "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "which": "bin/which"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/wide-align": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
+ "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "string-width": "^1.0.2 || 2"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/wrap-ansi": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
+ "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "ansi-styles": "^3.2.0",
+ "string-width": "^3.0.0",
+ "strip-ansi": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/wrap-ansi/node_modules/string-width": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "emoji-regex": "^7.0.1",
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/y18n": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
+ "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/solidity-coverage/node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/solidity-coverage/node_modules/yargs": {
+ "version": "13.3.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz",
+ "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "cliui": "^5.0.0",
+ "find-up": "^3.0.0",
+ "get-caller-file": "^2.0.1",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^2.0.0",
+ "set-blocking": "^2.0.0",
+ "string-width": "^3.0.0",
+ "which-module": "^2.0.0",
+ "y18n": "^4.0.0",
+ "yargs-parser": "^13.1.2"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/yargs-parser": {
+ "version": "13.1.2",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
+ "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "camelcase": "^5.0.0",
+ "decamelize": "^1.2.0"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/yargs-unparser": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz",
+ "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "flat": "^4.1.0",
+ "lodash": "^4.17.15",
+ "yargs": "^13.3.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/solidity-coverage/node_modules/yargs/node_modules/string-width": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "emoji-regex": "^7.0.1",
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz",
+ "integrity": "sha512-CBdZ2oa/BHhS4xj5DlhjWNHcan57/5YuvfdLf17iVmIpd9KRm+DFLmC6nBNj+6Ua7Kt3TmOjDpQT1aTYOQtoUA==",
+ "dev": true,
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "amdefine": ">=0.0.4"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/source-map-support": {
+ "version": "0.5.21",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
+ "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+ "dev": true,
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
+ "node_modules/source-map-support/node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/sshpk": {
+ "version": "1.17.0",
+ "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz",
+ "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "asn1": "~0.2.3",
+ "assert-plus": "^1.0.0",
+ "bcrypt-pbkdf": "^1.0.0",
+ "dashdash": "^1.12.0",
+ "ecc-jsbn": "~0.1.1",
+ "getpass": "^0.1.1",
+ "jsbn": "~0.1.0",
+ "safer-buffer": "^2.0.2",
+ "tweetnacl": "~0.14.0"
+ },
+ "bin": {
+ "sshpk-conv": "bin/sshpk-conv",
+ "sshpk-sign": "bin/sshpk-sign",
+ "sshpk-verify": "bin/sshpk-verify"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/sshpk/node_modules/tweetnacl": {
+ "version": "0.14.5",
+ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+ "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/stacktrace-parser": {
+ "version": "0.1.10",
+ "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz",
+ "integrity": "sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==",
+ "dev": true,
+ "dependencies": {
+ "type-fest": "^0.7.1"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/stacktrace-parser/node_modules/type-fest": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz",
+ "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/stealthy-require": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
+ "integrity": "sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/streamsearch": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
+ "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
+ "dev": true,
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "dev": true,
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
+ "node_modules/string-format": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/string-format/-/string-format-2.0.0.tgz",
+ "integrity": "sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/string-width": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+ "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+ "dev": true,
+ "dependencies": {
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/string-width/node_modules/ansi-regex": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz",
+ "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/string-width/node_modules/strip-ansi": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+ "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/string.prototype.trim": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz",
+ "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trimend": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz",
+ "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trimstart": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz",
+ "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-hex-prefix": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz",
+ "integrity": "sha512-q8d4ue7JGEiVcypji1bALTos+0pWtyGlivAWyPuTkHzuTCJqrK9sWxYQZUq6Nq3cuyv3bm734IhHvHtGGURU6A==",
+ "dev": true,
+ "dependencies": {
+ "is-hex-prefixed": "1.0.0"
+ },
+ "engines": {
+ "node": ">=6.5.0",
+ "npm": ">=3"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/sync-request": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/sync-request/-/sync-request-6.1.0.tgz",
+ "integrity": "sha512-8fjNkrNlNCrVc/av+Jn+xxqfCjYaBoHqCsDz6mt030UMxJGr+GSfCV1dQt2gRtlL63+VPidwDVLr7V2OcTSdRw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "http-response-object": "^3.0.1",
+ "sync-rpc": "^1.2.1",
+ "then-request": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/sync-rpc": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/sync-rpc/-/sync-rpc-1.3.6.tgz",
+ "integrity": "sha512-J8jTXuZzRlvU7HemDgHi3pGnh/rkoqR/OZSjhTyyZrEkkYQbk7Z33AXp37mkPfPpfdOuj7Ex3H/TJM1z48uPQw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "get-port": "^3.1.0"
+ }
+ },
+ "node_modules/table": {
+ "version": "6.8.1",
+ "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz",
+ "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==",
+ "dev": true,
+ "dependencies": {
+ "ajv": "^8.0.1",
+ "lodash.truncate": "^4.4.2",
+ "slice-ansi": "^4.0.0",
+ "string-width": "^4.2.3",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/table-layout": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-1.0.2.tgz",
+ "integrity": "sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "array-back": "^4.0.1",
+ "deep-extend": "~0.6.0",
+ "typical": "^5.2.0",
+ "wordwrapjs": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/table-layout/node_modules/array-back": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz",
+ "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/table-layout/node_modules/typical": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz",
+ "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/table/node_modules/ajv": {
+ "version": "8.12.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
+ "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
+ "dev": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/table/node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/table/node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "dev": true
+ },
+ "node_modules/table/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+ "dev": true
+ },
+ "node_modules/then-request": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/then-request/-/then-request-6.0.2.tgz",
+ "integrity": "sha512-3ZBiG7JvP3wbDzA9iNY5zJQcHL4jn/0BWtXIkagfz7QgOL/LqjCEOBQuJNZfu0XYnv5JhKh+cDxCPM4ILrqruA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@types/concat-stream": "^1.6.0",
+ "@types/form-data": "0.0.33",
+ "@types/node": "^8.0.0",
+ "@types/qs": "^6.2.31",
+ "caseless": "~0.12.0",
+ "concat-stream": "^1.6.0",
+ "form-data": "^2.2.0",
+ "http-basic": "^8.1.1",
+ "http-response-object": "^3.0.1",
+ "promise": "^8.0.0",
+ "qs": "^6.4.0"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/then-request/node_modules/@types/node": {
+ "version": "8.10.66",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.66.tgz",
+ "integrity": "sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/then-request/node_modules/form-data": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz",
+ "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.6",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 0.12"
+ }
+ },
+ "node_modules/tmp": {
+ "version": "0.0.33",
+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
+ "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
+ "dev": true,
+ "dependencies": {
+ "os-tmpdir": "~1.0.2"
+ },
+ "engines": {
+ "node": ">=0.6.0"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/tough-cookie": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
+ "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "psl": "^1.1.28",
+ "punycode": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+ "dev": true
+ },
+ "node_modules/ts-command-line-args": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/ts-command-line-args/-/ts-command-line-args-2.5.1.tgz",
+ "integrity": "sha512-H69ZwTw3rFHb5WYpQya40YAX2/w7Ut75uUECbgBIsLmM+BNuYnxsltfyyLMxy6sEeKxgijLTnQtLd0nKd6+IYw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "chalk": "^4.1.0",
+ "command-line-args": "^5.1.1",
+ "command-line-usage": "^6.1.0",
+ "string-format": "^2.0.0"
+ },
+ "bin": {
+ "write-markdown": "dist/write-markdown.js"
+ }
+ },
+ "node_modules/ts-command-line-args/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/ts-command-line-args/node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/ts-command-line-args/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/ts-command-line-args/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/ts-command-line-args/node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ts-command-line-args/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ts-essentials": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-7.0.3.tgz",
+ "integrity": "sha512-8+gr5+lqO3G84KdiTSMRLtuyJ+nTBVRKuCrK4lidMPdVeEp0uqC875uE5NMcaA7YYMN7XsNiFQuMvasF8HT/xQ==",
+ "dev": true,
+ "peer": true,
+ "peerDependencies": {
+ "typescript": ">=3.7.0"
+ }
+ },
+ "node_modules/ts-node": {
+ "version": "10.9.1",
+ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
+ "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@cspotcode/source-map-support": "^0.8.0",
+ "@tsconfig/node10": "^1.0.7",
+ "@tsconfig/node12": "^1.0.7",
+ "@tsconfig/node14": "^1.0.0",
+ "@tsconfig/node16": "^1.0.2",
+ "acorn": "^8.4.1",
+ "acorn-walk": "^8.1.1",
+ "arg": "^4.1.0",
+ "create-require": "^1.1.0",
+ "diff": "^4.0.1",
+ "make-error": "^1.1.1",
+ "v8-compile-cache-lib": "^3.0.1",
+ "yn": "3.1.1"
+ },
+ "bin": {
+ "ts-node": "dist/bin.js",
+ "ts-node-cwd": "dist/bin-cwd.js",
+ "ts-node-esm": "dist/bin-esm.js",
+ "ts-node-script": "dist/bin-script.js",
+ "ts-node-transpile-only": "dist/bin-transpile.js",
+ "ts-script": "dist/bin-script-deprecated.js"
+ },
+ "peerDependencies": {
+ "@swc/core": ">=1.2.50",
+ "@swc/wasm": ">=1.2.50",
+ "@types/node": "*",
+ "typescript": ">=2.7"
+ },
+ "peerDependenciesMeta": {
+ "@swc/core": {
+ "optional": true
+ },
+ "@swc/wasm": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ts-node/node_modules/diff": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
+ "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+ "dev": true
+ },
+ "node_modules/tsort": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/tsort/-/tsort-0.0.1.tgz",
+ "integrity": "sha512-Tyrf5mxF8Ofs1tNoxA13lFeZ2Zrbd6cKbuH3V+MQ5sb6DtBj5FjrXVsRWT8YvNAQTqNoz66dz1WsbigI22aEnw==",
+ "dev": true
+ },
+ "node_modules/tsutils": {
+ "version": "3.21.0",
+ "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz",
+ "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==",
+ "dev": true,
+ "dependencies": {
+ "tslib": "^1.8.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ },
+ "peerDependencies": {
+ "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta"
+ }
+ },
+ "node_modules/tunnel-agent": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+ "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/tweetnacl": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
+ "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==",
+ "dev": true
+ },
+ "node_modules/tweetnacl-util": {
+ "version": "0.15.1",
+ "resolved": "https://registry.npmjs.org/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz",
+ "integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==",
+ "dev": true
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/type-detect": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
+ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/typechain": {
+ "version": "8.3.1",
+ "resolved": "https://registry.npmjs.org/typechain/-/typechain-8.3.1.tgz",
+ "integrity": "sha512-fA7clol2IP/56yq6vkMTR+4URF1nGjV82Wx6Rf09EsqD4tkzMAvEaqYxVFCavJm/1xaRga/oD55K+4FtuXwQOQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@types/prettier": "^2.1.1",
+ "debug": "^4.3.1",
+ "fs-extra": "^7.0.0",
+ "glob": "7.1.7",
+ "js-sha3": "^0.8.0",
+ "lodash": "^4.17.15",
+ "mkdirp": "^1.0.4",
+ "prettier": "^2.3.1",
+ "ts-command-line-args": "^2.2.0",
+ "ts-essentials": "^7.0.1"
+ },
+ "bin": {
+ "typechain": "dist/cli/cli.js"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.3.0"
+ }
+ },
+ "node_modules/typechain/node_modules/glob": {
+ "version": "7.1.7",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz",
+ "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/typechain/node_modules/mkdirp": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
+ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
+ "dev": true,
+ "peer": true,
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/typed-array-buffer": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz",
+ "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.2.1",
+ "is-typed-array": "^1.1.10"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/typed-array-byte-length": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz",
+ "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "for-each": "^0.3.3",
+ "has-proto": "^1.0.1",
+ "is-typed-array": "^1.1.10"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typed-array-byte-offset": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz",
+ "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "available-typed-arrays": "^1.0.5",
+ "call-bind": "^1.0.2",
+ "for-each": "^0.3.3",
+ "has-proto": "^1.0.1",
+ "is-typed-array": "^1.1.10"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typed-array-length": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz",
+ "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "for-each": "^0.3.3",
+ "is-typed-array": "^1.1.9"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typedarray": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/typescript": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz",
+ "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==",
+ "dev": true,
+ "peer": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/typical": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz",
+ "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/uglify-js": {
+ "version": "3.17.4",
+ "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz",
+ "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==",
+ "dev": true,
+ "optional": true,
+ "peer": true,
+ "bin": {
+ "uglifyjs": "bin/uglifyjs"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/unbox-primitive": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
+ "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "has-bigints": "^1.0.2",
+ "has-symbols": "^1.0.3",
+ "which-boxed-primitive": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/undici": {
+ "version": "5.22.1",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-5.22.1.tgz",
+ "integrity": "sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw==",
+ "dev": true,
+ "dependencies": {
+ "busboy": "^1.6.0"
+ },
+ "engines": {
+ "node": ">=14.0"
+ }
+ },
+ "node_modules/unfetch": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz",
+ "integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==",
+ "dev": true
+ },
+ "node_modules/universalify": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/utf8": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz",
+ "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true
+ },
+ "node_modules/uuid": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+ "dev": true,
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
+ "node_modules/v8-compile-cache-lib": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
+ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/verror": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
+ "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==",
+ "dev": true,
+ "engines": [
+ "node >=0.6.0"
+ ],
+ "peer": true,
+ "dependencies": {
+ "assert-plus": "^1.0.0",
+ "core-util-is": "1.0.2",
+ "extsprintf": "^1.2.0"
+ }
+ },
+ "node_modules/w-json": {
+ "version": "1.3.10",
+ "resolved": "https://registry.npmjs.org/w-json/-/w-json-1.3.10.tgz",
+ "integrity": "sha512-XadVyw0xE+oZ5FGApXsdswv96rOhStzKqL53uSe5UaTadABGkWIg1+DTx8kiZ/VqTZTBneoL0l65RcPe4W3ecw==",
+ "dev": true
+ },
+ "node_modules/web3-utils": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.10.0.tgz",
+ "integrity": "sha512-kSaCM0uMcZTNUSmn5vMEhlo02RObGNRRCkdX0V9UTAU0+lrvn0HSaudyCo6CQzuXUsnuY2ERJGCGPfeWmv19Rg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "bn.js": "^5.2.1",
+ "ethereum-bloom-filters": "^1.0.6",
+ "ethereumjs-util": "^7.1.0",
+ "ethjs-unit": "0.1.6",
+ "number-to-bn": "1.7.0",
+ "randombytes": "^2.1.0",
+ "utf8": "3.0.0"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+ "dev": true
+ },
+ "node_modules/whatwg-url": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "dev": true,
+ "dependencies": {
+ "tr46": "~0.0.3",
+ "webidl-conversions": "^3.0.0"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/which-boxed-primitive": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz",
+ "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "is-bigint": "^1.0.1",
+ "is-boolean-object": "^1.1.0",
+ "is-number-object": "^1.0.4",
+ "is-string": "^1.0.5",
+ "is-symbol": "^1.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-module": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
+ "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/which-typed-array": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz",
+ "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "available-typed-arrays": "^1.0.5",
+ "call-bind": "^1.0.2",
+ "for-each": "^0.3.3",
+ "gopd": "^1.0.1",
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/wide-align": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
+ "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
+ "dev": true,
+ "dependencies": {
+ "string-width": "^1.0.2 || 2 || 3 || 4"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/wordwrap": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
+ "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/wordwrapjs": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-4.0.1.tgz",
+ "integrity": "sha512-kKlNACbvHrkpIw6oPeYDSmdCTu2hdMHoyXLTcUKala++lx5Y+wjJ/e474Jqv5abnVmwxw08DiTuHmw69lJGksA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "reduce-flatten": "^2.0.0",
+ "typical": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/wordwrapjs/node_modules/typical": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz",
+ "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/workerpool": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz",
+ "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==",
+ "dev": true
+ },
+ "node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true
+ },
+ "node_modules/ws": {
+ "version": "7.4.6",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
+ "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==",
+ "dev": true,
+ "engines": {
+ "node": ">=8.3.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": "^5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/xmlhttprequest": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz",
+ "integrity": "sha512-58Im/U0mlVBLM38NdZjHyhuMtCqa61469k2YP/AaPbvCoV9aQGUpbJBj1QRm2ytRiVQBD/fsw7L2bJGDVQswBA==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true
+ },
+ "node_modules/yargs": {
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "dev": true,
+ "dependencies": {
+ "cliui": "^8.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.3",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^21.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "20.2.4",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz",
+ "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yargs-unparser": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz",
+ "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==",
+ "dev": true,
+ "dependencies": {
+ "camelcase": "^6.0.0",
+ "decamelize": "^4.0.0",
+ "flat": "^5.0.2",
+ "is-plain-obj": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yargs/node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yn": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
+ "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ }
+ }
+}
diff --git a/contracts/package.json b/contracts/package.json
new file mode 100644
index 000000000..c63353655
--- /dev/null
+++ b/contracts/package.json
@@ -0,0 +1,49 @@
+{
+ "name": "smart_contract",
+ "engines": {
+ "node": "18.12.1",
+ "npm": "9.6.1"
+ },
+ "scripts": {
+ "build": "npx hardhat compile",
+ "test": "npx hardhat test",
+ "test:reportgas": "REPORT_GAS=true npx hardhat test",
+ "test-zkevm-all": "npx hardhat test test/ZkEvm.ts test/Bridging.ts test/ZkEvmBridge.ts",
+ "test-zkevm": "npx hardhat test test/ZkEvm.ts",
+ "test-token-bridge": "npx hardhat test test/tokenBridge/BridgedToken.ts test/tokenBridge/E2E.ts test/tokenBridge/TokenBridge.ts",
+ "coverage": "npx hardhat coverage",
+ "balance": "ts-node scripts/balanceOf.ts",
+ "solhint": "npx solhint 'contracts/**/*.sol'",
+ "fmt:js:check": "prettier -c '**/*.{js,ts}'",
+ "fmt:js": "prettier -w '**/*.{js,ts}'",
+ "lint:js:check": "npx eslint '**/*.{js,ts}'",
+ "lint:js": "npx eslint --fix '**/*.{js,ts}'",
+ "fmt:sol:check": "prettier -c 'contracts/**/*.sol'",
+ "fmt:sol": "prettier -w 'contracts/**/*.sol'"
+ },
+ "devDependencies": {
+ "@gnosis.pm/zodiac": "^3.3.2",
+ "@nomicfoundation/hardhat-toolbox": "^2.0.2",
+ "@openzeppelin/contracts": "^4.9.2",
+ "@openzeppelin/contracts-upgradeable": "^4.9.2",
+ "@openzeppelin/hardhat-upgrades": "^1.28.0",
+ "@types/diff": "^5.0.3",
+ "@types/npmlog": "^4.1.4",
+ "@types/yargs": "^17.0.24",
+ "@typescript-eslint/eslint-plugin": "^5.59.0",
+ "@typescript-eslint/parser": "^5.59.0",
+ "colors": "^1.4.0",
+ "dotenv": "^16.0.3",
+ "edit-json-file": "^1.7.0",
+ "eslint": "^8.38.0",
+ "eslint-config-prettier": "^8.8.0",
+ "hardhat": "^2.14.0",
+ "hardhat-tracer": "^2.2.2",
+ "npmlog": "^7.0.1",
+ "prettier": "^2.8.7",
+ "prettier-plugin-solidity": "^1.1.3",
+ "solhint": "^3.4.1",
+ "solhint-plugin-prettier": "^0.0.5",
+ "yargs": "^17.7.1"
+ }
+}
diff --git a/contracts/scripts/balanceOf.ts b/contracts/scripts/balanceOf.ts
new file mode 100644
index 000000000..6b64a9812
--- /dev/null
+++ b/contracts/scripts/balanceOf.ts
@@ -0,0 +1,28 @@
+// Test agnostic script for
+import fs from "fs";
+import path from "path";
+import { ethers } from "ethers";
+import { getBlockchainNode } from "../common";
+
+async function main() {
+ const blockchainNode = getBlockchainNode();
+ const provider = new ethers.providers.JsonRpcProvider(blockchainNode);
+ const keyFiles = process.argv.slice(2);
+ const addresses = keyFiles
+ .map((filePath) => JSON.parse(fs.readFileSync(filePath, "utf8")))
+ .map((keyPair) => (keyPair.account_key ? keyPair.account_key.addr : keyPair.address));
+ const balances = await Promise.all(addresses.map((address) => provider.getBalance(address)));
+ for (let i = 0; i < balances.length; i++) {
+ const keyFile = path.basename(keyFiles[i]).padStart(20, " ");
+ const address = addresses[i];
+ const balance = ethers.utils.formatEther(balances[i]).padEnd(25, " ");
+ console.log(`Balance of ${keyFile} on ${address} is ${balance} ETH`);
+ }
+}
+
+main()
+ .then(() => process.exit(0))
+ .catch((error) => {
+ console.error(error);
+ process.exit(1);
+ });
diff --git a/contracts/scripts/cli.ts b/contracts/scripts/cli.ts
new file mode 100644
index 000000000..06bad415a
--- /dev/null
+++ b/contracts/scripts/cli.ts
@@ -0,0 +1,49 @@
+const HEXADECIMAL_REGEX = new RegExp("^0[xX][0-9a-fA-F]+$");
+
+const ADDRESS_HEX_STR_SIZE = 42;
+const PRIVKEY_HEX_STR_SIZE = 66;
+const HASH_HEX_STR_SIZE = 66;
+
+function sanitizeHexBytes(paramName: string, value: string, expectedSize: number) {
+ // TODO: add hexadecimal regex match
+ if (!value.startsWith("0x")) {
+ value = "0x" + value;
+ }
+
+ if (!HEXADECIMAL_REGEX.test(value)) {
+ throw new Error(`${paramName}: '${value}' is not a valid Hexadecimal notation!`);
+ }
+ if (value.length !== expectedSize) {
+ throw new Error(`${paramName} has size ${value.length} expected ${expectedSize}`);
+ }
+ return value;
+}
+
+function sanitizeAddress(argName: string) {
+ return (input: string) => {
+ return sanitizeHexBytes(argName, input, ADDRESS_HEX_STR_SIZE);
+ };
+}
+
+function sanitizePrivKey(argName: string) {
+ return (input: string) => {
+ return sanitizeHexBytes(argName, input, PRIVKEY_HEX_STR_SIZE);
+ };
+}
+
+function sanitizeHash(argName: string) {
+ return (input: string) => {
+ return sanitizeHexBytes(argName, input, HASH_HEX_STR_SIZE);
+ };
+}
+
+function assertIsNumber(argName: string) {
+ return (input: string) => {
+ if (typeof input != "number" || isNaN(input)) {
+ throw Error(`${argName} must be a valid number. Got: '${input}'`);
+ }
+ return input;
+ };
+}
+
+export { sanitizeHexBytes, sanitizeAddress, sanitizePrivKey, sanitizeHash, assertIsNumber };
diff --git a/contracts/scripts/deployment/deployL2MessageService.ts b/contracts/scripts/deployment/deployL2MessageService.ts
new file mode 100644
index 000000000..8bfedf954
--- /dev/null
+++ b/contracts/scripts/deployment/deployL2MessageService.ts
@@ -0,0 +1,43 @@
+import { deployUpgradableFromFactory, requireEnv } from "../hardhat/utils";
+
+/*
+ *******************************************************************************************
+ 1. Set the L2MSGSERVICE_SECURITY_COUNCIL - e.g EOA or Safe
+ 2. Set the L2MSGSERVICE_L1L2_MESSAGE_SETTER for message hash anchoring
+ 3. Set the L2MSGSERVICE_RATE_LIMIT_PERIOD in Seconds
+ 4. Set the L2MSGSERVICE_RATE_LIMIT_AMOUNT in Wei
+ *******************************************************************************************
+ NB: use the verifier.address output as input for scripts/deployment/setVerifierAddress.ts
+ *******************************************************************************************
+ npx hardhat run --network zkevm_dev scripts/deployment/deployL2MessageService.ts
+ *******************************************************************************************
+*/
+
+async function main() {
+ const L2MessageService_securityCouncil = requireEnv("L2MSGSERVICE_SECURITY_COUNCIL");
+ const L2MessageService_l1l2MessageSetter = requireEnv("L2MSGSERVICE_L1L2_MESSAGE_SETTER");
+ const L2MessageService_rateLimitPeriod = requireEnv("L2MSGSERVICE_RATE_LIMIT_PERIOD");
+ const L2MessageService_rateLimitAmount = requireEnv("L2MSGSERVICE_RATE_LIMIT_AMOUNT");
+
+ const L2implementation = await deployUpgradableFromFactory(
+ "L2MessageService",
+ [
+ L2MessageService_securityCouncil,
+ L2MessageService_l1l2MessageSetter,
+ L2MessageService_rateLimitPeriod,
+ L2MessageService_rateLimitAmount,
+ ],
+ {
+ initializer: "initialize(address,address,uint256,uint256)",
+ unsafeAllow: ["constructor"],
+ },
+ );
+ console.log(`L2MessageService deployed at ${L2implementation.address}`);
+}
+
+main()
+ .then(() => process.exit(0))
+ .catch((error) => {
+ console.error(error);
+ process.exit(1);
+ });
diff --git a/contracts/scripts/deployment/deployTimelock.ts b/contracts/scripts/deployment/deployTimelock.ts
new file mode 100644
index 000000000..8d04e129f
--- /dev/null
+++ b/contracts/scripts/deployment/deployTimelock.ts
@@ -0,0 +1,52 @@
+import { ethers } from "hardhat";
+import { deployFromFactory, requireEnv } from "../hardhat/utils";
+import { get1559Fees } from "../utils";
+
+/*
+ *******************************************************************************************
+ 1. Set the Safe address for TIMELOCK_PROPOSERS
+ 2. Set the Safe address for TIMELOCK_EXECUTORS
+ 3. Set the Safe address for the TIMELOCK_ADMIN_ADDRESS / optional see note below
+ *******************************************************************************************
+ IMPORTANT: The optional admin can aid with initial configuration of roles after deployment
+ without being subject to delay, but this role should be subsequently renounced in favor of
+ administration through timelocked proposals. Previous versions of this contract would assign
+ this admin to the deployer automatically and should be renounced as well.
+ *******************************************************************************************
+ npx hardhat run --network zkevm_dev scripts/deployment/deployTimelock.ts
+ *******************************************************************************************
+*/
+
+async function main() {
+ const provider = ethers.provider;
+
+ // This should be the safe
+ const timeLockProposers = requireEnv("TIMELOCK_PROPOSERS");
+
+ // This should be the safe
+ const timelockExecutors = requireEnv("TIMELOCK_EXECUTORS");
+
+ // This should be the safe
+ const adminAddress = requireEnv("TIMELOCK_ADMIN_ADDRESS");
+
+ const minDelay = process.env.MIN_DELAY || 0;
+
+ const timelock = await deployFromFactory(
+ "TimeLock",
+ provider,
+ minDelay,
+ timeLockProposers?.split(","),
+ timelockExecutors?.split(","),
+ adminAddress,
+ await get1559Fees(provider),
+ );
+
+ console.log("TimeLock deployed to:", timelock.address);
+}
+
+main()
+ .then(() => process.exit(0))
+ .catch((error) => {
+ console.error(error);
+ process.exit(1);
+ });
diff --git a/contracts/scripts/deployment/deployVerifier.ts b/contracts/scripts/deployment/deployVerifier.ts
new file mode 100644
index 000000000..4603087ab
--- /dev/null
+++ b/contracts/scripts/deployment/deployVerifier.ts
@@ -0,0 +1,39 @@
+import { run } from "hardhat";
+import { delay } from "../../utils/storeAddress";
+import { deployFromFactory } from "../hardhat/utils";
+
+/*
+ *******************************************************************************************
+ 1. Set the VERIFIER_CONTRACT_NAME - e.g PlonkeVerifyFull
+ *******************************************************************************************
+ NB: use the verifier.address output as input for scripts/deployment/setVerifierAddress.ts
+ *******************************************************************************************
+ npx hardhat run --network zkevm_dev scripts/deployment/deployVerifier.ts
+ *******************************************************************************************
+*/
+
+async function main() {
+ const verifierFull = await deployFromFactory("PlonkVerifierFull");
+ console.log(`PlonkVerifierFull deployed at ${verifierFull.address}`);
+
+ const verifierFullLarge = await deployFromFactory("PlonkVerifierFullLarge");
+ console.log(`PlonkVerifierFullLarge deployed at ${verifierFullLarge.address}`);
+
+ console.log(`Waiting for 2 minutes before verifying`)
+ await delay(120_000);
+
+ await run("verify", {
+ address: verifierFull.address,
+ });
+
+ await run("verify", {
+ address: verifierFullLarge.address,
+ });
+}
+
+main()
+ .then(() => process.exit(0))
+ .catch((error) => {
+ console.error(error);
+ process.exit(1);
+ });
diff --git a/contracts/scripts/deployment/deployZkEVM.ts b/contracts/scripts/deployment/deployZkEVM.ts
new file mode 100644
index 000000000..96262af83
--- /dev/null
+++ b/contracts/scripts/deployment/deployZkEVM.ts
@@ -0,0 +1,63 @@
+import { deployFromFactory, deployUpgradableFromFactory, requireEnv } from "../hardhat/utils";
+
+/*
+ *******************************************************************************************
+ 1. Set the ZKEVMV2_INITIAL_STATE_ROOT_HASH
+ 2. Set the ZKEVMV2_INITIAL_L2_BLOCK_NUMBER
+ 3. Set the ZKEVMV2_SECURITY_COUNCIL
+ 4. Set the ZKEVMV2_OPERATORS
+ 5. Set the ZKEVMV2_RATE_LIMIT_PERIOD in Seconds
+ 6. Set the ZKEVMV2_RATE_LIMIT_AMOUNT in Wei
+ *******************************************************************************************
+ NB: use the verifier.address output as input for scripts/deployment/setVerifierAddress.ts
+ *******************************************************************************************
+ npx hardhat run --network zkevm_dev scripts/deployment/deployZkEVM.ts
+ *******************************************************************************************
+*/
+
+async function main() {
+ const verifierContractName = process.env.VERIFIER_CONTRACT_NAME || "PlonkVerifier";
+
+ // PLONK VERIFIER
+ const verifier = await deployFromFactory(verifierContractName);
+ console.log(`PlonkVerifier deployed at ${verifier.address}`);
+
+ await deployZKEVM(verifier.address);
+}
+
+async function deployZKEVM(verifierAddress: string) {
+ // ZKEVMV2 DEPLOYED AS UPGRADEABLE PROXY
+ const ZkEvmV2_initialStateRootHash = requireEnv("ZKEVMV2_INITIAL_STATE_ROOT_HASH");
+ const ZkEvmV2_initialL2BlockNumber = requireEnv("ZKEVMV2_INITIAL_L2_BLOCK_NUMBER");
+ const ZKEVMV2_securityCouncil = requireEnv("ZKEVMV2_SECURITY_COUNCIL");
+ const ZKEVMV2_operators = requireEnv("ZKEVMV2_OPERATORS");
+ const ZKEVMV2_rateLimitPeriodInSeconds = requireEnv("ZKEVMV2_RATE_LIMIT_PERIOD");
+ const ZKEVMV2_rateLimitAmountInWei = requireEnv("ZKEVMV2_RATE_LIMIT_AMOUNT");
+
+ console.log(`Setting operators ${ZKEVMV2_operators}`);
+ const zkEvmV2 = await deployUpgradableFromFactory(
+ "ZkEvmV2",
+ [
+ ZkEvmV2_initialStateRootHash,
+ ZkEvmV2_initialL2BlockNumber,
+ verifierAddress,
+ ZKEVMV2_securityCouncil,
+ ZKEVMV2_operators?.split(","),
+ ZKEVMV2_rateLimitPeriodInSeconds,
+ ZKEVMV2_rateLimitAmountInWei,
+ ],
+ {
+ initializer: "initialize(bytes32,uint256,address,address,address[],uint256,uint256)",
+ unsafeAllow: ["constructor"],
+ },
+ );
+
+ console.log(`ZkEvmV2 deployed at ${zkEvmV2.address}`);
+}
+
+main()
+ .then(() => process.exit(0))
+ .catch((error) => {
+ console.error(error);
+ process.exit(1);
+ });
diff --git a/contracts/scripts/deployment/deployZkEvmV2.ts b/contracts/scripts/deployment/deployZkEvmV2.ts
new file mode 100644
index 000000000..708c5d994
--- /dev/null
+++ b/contracts/scripts/deployment/deployZkEvmV2.ts
@@ -0,0 +1,49 @@
+import { ethers, run, upgrades } from "hardhat";
+import { delay } from "../../utils/storeAddress";
+import { requireEnv } from "../hardhat/utils";
+
+/*
+ *******************************************************************************************
+
+ *******************************************************************************************
+ *******************************************************************************************
+ npx hardhat run --network zkevm_dev scripts/deployment/deployZkEvmV2.ts
+ *******************************************************************************************
+*/
+
+async function main() {
+ const proxyAddress = requireEnv("ZKEVMV2_ADDRESS");
+
+ const factory = await ethers.getContractFactory("ZkEvmV2");
+
+ console.log("Deploying V2 Contract...");
+ const contractAddress = await upgrades.deployImplementation(factory, {
+ kind: "transparent",
+ });
+
+ console.log(`Contract deployed at ${contractAddress}`);
+
+ const upgradeCallUsingSecurityCouncil = ethers.utils.hexConcat([
+ "0x99a88ec4",
+ ethers.utils.defaultAbiCoder.encode(["address", "address"], [proxyAddress, contractAddress]),
+ ]);
+
+ console.log(
+ "Encoded Tx Upgrade from Security Council:",
+ "\n",
+ upgradeCallUsingSecurityCouncil
+ );
+
+ await delay(120_000);
+
+ await run("verify", {
+ address: contractAddress,
+ });
+}
+
+main()
+ .then(() => process.exit(0))
+ .catch((error) => {
+ console.error(error);
+ process.exit(1);
+ });
diff --git a/contracts/scripts/deployment/deployZkEvmV2WithReinitialization.ts b/contracts/scripts/deployment/deployZkEvmV2WithReinitialization.ts
new file mode 100644
index 000000000..f6213df32
--- /dev/null
+++ b/contracts/scripts/deployment/deployZkEvmV2WithReinitialization.ts
@@ -0,0 +1,56 @@
+import { ethers, upgrades } from "hardhat";
+import { requireEnv } from "../hardhat/utils";
+import { ZkEvmV2Init__factory } from "../../typechain-types";
+
+/*
+ *******************************************************************************************
+
+ *******************************************************************************************
+ *******************************************************************************************
+ npx hardhat run --network zkevm_dev scripts/deployment/deployImplementation.ts
+ *******************************************************************************************
+*/
+
+async function main() {
+ const proxyAddress = requireEnv("ZKEVMV2_ADDRESS");
+ const initialL2BlockNumber = "3";
+ const initialStateRootHash = "0x3450000000000000000000000000000000000000000000000000000000000000";
+
+ const factory = await ethers.getContractFactory("ZkEvmV2Init");
+
+ console.log("Deploying V2 Contract...");
+ const v2contract = await upgrades.deployImplementation(factory, {
+ kind: "transparent",
+ });
+
+ console.log(`Contract deployed at ${v2contract}`);
+
+ const upgradeCallWithReinitializationUsingSecurityCouncil = ethers.utils.hexConcat([
+ "0x9623609d",
+ ethers.utils.defaultAbiCoder.encode(
+ ["address", "address", "bytes"],
+ [
+ proxyAddress,
+ v2contract,
+ ZkEvmV2Init__factory.createInterface().encodeFunctionData("initializeV2", [
+ initialL2BlockNumber,
+ initialStateRootHash,
+ ]),
+ ],
+ ),
+ ]);
+
+ console.log(
+ "Encoded Tx Upgrade with Reinitialization from Security Council:",
+ "\n",
+ upgradeCallWithReinitializationUsingSecurityCouncil,
+ );
+ console.log("\n");
+}
+
+main()
+ .then(() => process.exit(0))
+ .catch((error) => {
+ console.error(error);
+ process.exit(1);
+ });
diff --git a/contracts/scripts/gnosis/create4-8SafeL1.ts b/contracts/scripts/gnosis/create4-8SafeL1.ts
new file mode 100644
index 000000000..d1e202285
--- /dev/null
+++ b/contracts/scripts/gnosis/create4-8SafeL1.ts
@@ -0,0 +1,40 @@
+/* eslint-disable @typescript-eslint/no-var-requires */
+import { requireEnv } from "../hardhat/utils";
+import { SafeFactory } from "@safe-global/protocol-kit";
+const { EthersAdapter } = require("@safe-global/protocol-kit");
+const { ethers } = require("ethers");
+
+const main = async () => {
+ const RPC_URL = requireEnv("BLOCKCHAIN_NODE");
+ const SAFE_OWNER1_PRIVATE_KEY = requireEnv("SAFE_OWNER1_PRIVATE_KEY");
+ const SAFE_OWNERS = requireEnv("SAFE_OWNERS");
+ const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
+ const signer = new ethers.Wallet(SAFE_OWNER1_PRIVATE_KEY, provider);
+
+ const ethAdapter = new EthersAdapter({ ethers, signerOrProvider: signer });
+
+ const chainId = await ethAdapter.getChainId();
+ console.log(`ChainId: ${chainId}`);
+
+ // const safeVersion = '1.3.0'
+ // const isL1SafeMasterCopy = false
+ const safeFactory = await SafeFactory.create({ ethAdapter: ethAdapter });
+
+ const safeAccountConfig = {
+ threshold: 4, // Setting the Threshold to 4
+ owners: SAFE_OWNERS?.split(","),
+ };
+ console.log("Deploying 4/8 safe..");
+
+ const safeSdkOwner1 = await safeFactory.deploySafe({ safeAccountConfig });
+ const safeAddress = await safeSdkOwner1.getAddress();
+
+ console.log(`4/8 Safe deployed at: ${safeAddress}`);
+};
+
+main()
+ .then(() => process.exit(0))
+ .catch((error) => {
+ console.error(error);
+ process.exit(1);
+ });
diff --git a/contracts/scripts/gnosis/create4-8SafeL2.ts b/contracts/scripts/gnosis/create4-8SafeL2.ts
new file mode 100644
index 000000000..bf19c1fab
--- /dev/null
+++ b/contracts/scripts/gnosis/create4-8SafeL2.ts
@@ -0,0 +1,47 @@
+/* eslint-disable @typescript-eslint/no-var-requires */
+import { requireEnv } from "../hardhat/utils";
+import { SafeFactory } from "@safe-global/protocol-kit";
+const { EthersAdapter } = require("@safe-global/protocol-kit");
+const { ethers } = require("ethers");
+import { get1559Fees } from "../utils";
+
+const main = async () => {
+ const RPC_URL = requireEnv("BLOCKCHAIN_NODE");
+ const SAFE_OWNER1_PRIVATE_KEY = requireEnv("SAFE_OWNER1_PRIVATE_KEY");
+ const SAFE_OWNERS = requireEnv("SAFE_OWNERS");
+ const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
+ const signer = new ethers.Wallet(SAFE_OWNER1_PRIVATE_KEY, provider);
+
+ const ethAdapter = new EthersAdapter({ ethers, signerOrProvider: signer });
+
+ const chainId = await ethAdapter.getChainId();
+ console.log(`ChainId: ${chainId}`);
+ const eip1559Fees = await get1559Fees(provider);
+
+ const txOptions = {
+ maxFeePerGas: eip1559Fees.maxFeePerGas?.toString(),
+ maxPriorityFeePerGas: eip1559Fees.maxPriorityFeePerGas?.toString(),
+ };
+
+ // const safeVersion = '1.3.0'
+ // const isL1SafeMasterCopy = false
+ const safeFactory = await SafeFactory.create({ ethAdapter: ethAdapter });
+
+ const safeAccountConfig = {
+ threshold: 4, // Setting the Threshold to 4
+ owners: SAFE_OWNERS?.split(","),
+ };
+ console.log("Deploying 4/8 safe..");
+
+ const safeSdkOwner1 = await safeFactory.deploySafe({ safeAccountConfig, saltNonce: "123", options: txOptions });
+ const safeAddress = await safeSdkOwner1.getAddress();
+
+ console.log(`4/8 Safe deployed at: ${safeAddress}`);
+};
+
+main()
+ .then(() => process.exit(0))
+ .catch((error) => {
+ console.error(error);
+ process.exit(1);
+ });
diff --git a/contracts/scripts/gnosis/create5-8SafeL1.ts b/contracts/scripts/gnosis/create5-8SafeL1.ts
new file mode 100644
index 000000000..78a437ee5
--- /dev/null
+++ b/contracts/scripts/gnosis/create5-8SafeL1.ts
@@ -0,0 +1,40 @@
+/* eslint-disable @typescript-eslint/no-var-requires */
+import { requireEnv } from "../hardhat/utils";
+import { SafeFactory } from "@safe-global/protocol-kit";
+const { EthersAdapter } = require("@safe-global/protocol-kit");
+const { ethers } = require("ethers");
+
+const main = async () => {
+ const RPC_URL = requireEnv("BLOCKCHAIN_NODE");
+ const SAFE_OWNER1_PRIVATE_KEY = requireEnv("SAFE_OWNER1_PRIVATE_KEY");
+ const SAFE_OWNERS = requireEnv("SAFE_OWNERS");
+ const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
+ const signer = new ethers.Wallet(SAFE_OWNER1_PRIVATE_KEY, provider);
+
+ const ethAdapter = new EthersAdapter({ ethers, signerOrProvider: signer });
+
+ const chainId = await ethAdapter.getChainId();
+ console.log(`ChainId: ${chainId}`);
+
+ // const safeVersion = '1.3.0'
+ // const isL1SafeMasterCopy = false
+ const safeFactory = await SafeFactory.create({ ethAdapter: ethAdapter });
+
+ const safeAccountConfig = {
+ threshold: 5, // Setting the Threshold to 5
+ owners: SAFE_OWNERS?.split(","),
+ };
+ console.log("Deploying 5/8 safe..");
+
+ const safeSdkOwner1 = await safeFactory.deploySafe({ safeAccountConfig });
+ const safeAddress = safeSdkOwner1.getAddress();
+
+ console.log(`5/8 Safe deployed at: ${safeAddress}`);
+};
+
+main()
+ .then(() => process.exit(0))
+ .catch((error) => {
+ console.error(error);
+ process.exit(1);
+ });
diff --git a/contracts/scripts/gnosis/create5-8SafeL2.ts b/contracts/scripts/gnosis/create5-8SafeL2.ts
new file mode 100644
index 000000000..edd63bd3f
--- /dev/null
+++ b/contracts/scripts/gnosis/create5-8SafeL2.ts
@@ -0,0 +1,47 @@
+/* eslint-disable @typescript-eslint/no-var-requires */
+import { requireEnv } from "../hardhat/utils";
+import { SafeFactory } from "@safe-global/protocol-kit";
+const { EthersAdapter } = require("@safe-global/protocol-kit");
+const { ethers } = require("ethers");
+import { get1559Fees } from "../utils";
+
+const main = async () => {
+ const RPC_URL = requireEnv("BLOCKCHAIN_NODE");
+ const SAFE_OWNER1_PRIVATE_KEY = requireEnv("SAFE_OWNER1_PRIVATE_KEY");
+ const SAFE_OWNERS = requireEnv("SAFE_OWNERS");
+ const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
+ const signer = new ethers.Wallet(SAFE_OWNER1_PRIVATE_KEY, provider);
+
+ const ethAdapter = new EthersAdapter({ ethers, signerOrProvider: signer });
+
+ const chainId = await ethAdapter.getChainId();
+ console.log(`ChainId: ${chainId}`);
+ const eip1559Fees = await get1559Fees(provider);
+
+ const txOptions = {
+ maxFeePerGas: eip1559Fees.maxFeePerGas?.toString(),
+ maxPriorityFeePerGas: eip1559Fees.maxPriorityFeePerGas?.toString(),
+ };
+
+ // const safeVersion = '1.3.0'
+ // const isL1SafeMasterCopy = false
+ const safeFactory = await SafeFactory.create({ ethAdapter: ethAdapter });
+
+ const safeAccountConfig = {
+ threshold: 5, // Setting the Threshold to 5
+ owners: SAFE_OWNERS?.split(","),
+ };
+ console.log("Deploying 5/8 safe..");
+
+ const safeSdkOwner1 = await safeFactory.deploySafe({ safeAccountConfig, saltNonce: "123", options: txOptions });
+ const safeAddress = safeSdkOwner1.getAddress();
+
+ console.log(`5/8 Safe deployed at: ${safeAddress}`);
+};
+
+main()
+ .then(() => process.exit(0))
+ .catch((error) => {
+ console.error(error);
+ process.exit(1);
+ });
diff --git a/contracts/scripts/gnosis/createSecurityCouncilSafeL1.ts b/contracts/scripts/gnosis/createSecurityCouncilSafeL1.ts
new file mode 100644
index 000000000..0ff884b00
--- /dev/null
+++ b/contracts/scripts/gnosis/createSecurityCouncilSafeL1.ts
@@ -0,0 +1,38 @@
+import { ethers } from "hardhat";
+import { requireEnv } from "../hardhat/utils";
+import { SafeFactory, EthersAdapter } from "@safe-global/protocol-kit";
+
+const main = async () => {
+ const RPC_URL = requireEnv("BLOCKCHAIN_NODE");
+ const SAFE_OWNER1_PRIVATE_KEY = requireEnv("SAFE_OWNER1_PRIVATE_KEY");
+ const SAFE_OWNERS = requireEnv("SAFE_OWNERS");
+ const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
+ const signer = new ethers.Wallet(SAFE_OWNER1_PRIVATE_KEY, provider);
+
+ const ethAdapter = new EthersAdapter({ ethers, signerOrProvider: signer });
+
+ const chainId = await ethAdapter.getChainId();
+ console.log(`ChainId: ${chainId}`);
+
+ // const safeVersion = '1.3.0'
+ // const isL1SafeMasterCopy = false
+ const safeFactory = await SafeFactory.create({ ethAdapter: ethAdapter });
+
+ const safeAccountConfig = {
+ threshold: 1, // Setting the Threshold to 1
+ owners: SAFE_OWNERS?.split(","),
+ };
+ console.log("Deploying Security Council safe..");
+
+ const safeSdkOwner1 = await safeFactory.deploySafe({ safeAccountConfig });
+ const safeAddress = await safeSdkOwner1.getAddress();
+
+ console.log(`Security Council Safe deployed at: ${safeAddress}`);
+};
+
+main()
+ .then(() => process.exit(0))
+ .catch((error) => {
+ console.error(error);
+ process.exit(1);
+ });
diff --git a/contracts/scripts/gnosis/createSecurityCouncilSafeL2.ts b/contracts/scripts/gnosis/createSecurityCouncilSafeL2.ts
new file mode 100644
index 000000000..89d899c98
--- /dev/null
+++ b/contracts/scripts/gnosis/createSecurityCouncilSafeL2.ts
@@ -0,0 +1,45 @@
+import { ethers } from "hardhat";
+import { requireEnv } from "../hardhat/utils";
+import { SafeFactory, EthersAdapter } from "@safe-global/protocol-kit";
+import { get1559Fees } from "../utils";
+
+const main = async () => {
+ const RPC_URL = requireEnv("BLOCKCHAIN_NODE");
+ const SAFE_OWNER1_PRIVATE_KEY = requireEnv("SAFE_OWNER1_PRIVATE_KEY");
+ const SAFE_OWNERS = requireEnv("SAFE_OWNERS");
+ const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
+ const signer = new ethers.Wallet(SAFE_OWNER1_PRIVATE_KEY, provider);
+
+ const ethAdapter = new EthersAdapter({ ethers, signerOrProvider: signer });
+
+ const chainId = await ethAdapter.getChainId();
+ console.log(`ChainId: ${chainId}`);
+ const eip1559Fees = await get1559Fees(provider);
+
+ // const safeVersion = '1.3.0'
+ // const isL1SafeMasterCopy = false
+ const safeFactory = await SafeFactory.create({ ethAdapter: ethAdapter });
+
+ const safeAccountConfig = {
+ threshold: 1, // Setting the Threshold to 1
+ owners: SAFE_OWNERS?.split(","),
+ };
+ console.log("Deploying Security Council safe..");
+
+ const txOptions = {
+ maxFeePerGas: eip1559Fees.maxFeePerGas?.toString(),
+ maxPriorityFeePerGas: eip1559Fees.maxPriorityFeePerGas?.toString(),
+ };
+
+ const safeSdkOwner1 = await safeFactory.deploySafe({ safeAccountConfig, saltNonce: "123", options: txOptions });
+ const safeAddress = await safeSdkOwner1.getAddress();
+
+ console.log(`Security Council Safe deployed at: ${safeAddress}`);
+};
+
+main()
+ .then(() => process.exit(0))
+ .catch((error) => {
+ console.error(error);
+ process.exit(1);
+ });
diff --git a/contracts/scripts/gnosis/createZodiacL1.ts b/contracts/scripts/gnosis/createZodiacL1.ts
new file mode 100644
index 000000000..ee925ea68
--- /dev/null
+++ b/contracts/scripts/gnosis/createZodiacL1.ts
@@ -0,0 +1,529 @@
+import { ethers } from "hardhat";
+import { requireEnv } from "../hardhat/utils";
+import Safe, { EthersAdapter } from "@safe-global/protocol-kit";
+import { deployAndSetUpModule, KnownContracts } from "@gnosis.pm/zodiac";
+import { SafeTransactionDataPartial } from "@safe-global/safe-core-sdk-types";
+import { OPERATOR_ROLE } from "../../test/utils/constants";
+
+const main = async () => {
+ const RPC_URL = requireEnv("BLOCKCHAIN_NODE");
+ const SAFE_OWNER1_PRIVATE_KEY = requireEnv("SAFE_OWNER1_PRIVATE_KEY");
+ // const SAFE_OWNERS = requireEnv("SAFE_OWNERS");
+ const safe4OutOf8Contract = requireEnv("SAFE_4_OUT_OF_8_CONTRACT");
+ const safe5OutOf8Contract = requireEnv("SAFE_5_OUT_OF_8_CONTRACT");
+ const timelockContract = requireEnv("TIMELOCK_CONTRACT");
+ const proxyContract = requireEnv("PROXY_CONTRACT");
+
+ const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
+ const signer = new ethers.Wallet(SAFE_OWNER1_PRIVATE_KEY, provider);
+ const deployedSafeAddress = requireEnv("SAFE_SECURITY_COUNCIL_ADDRESS");
+
+ const ethAdapter = new EthersAdapter({ ethers, signerOrProvider: signer });
+
+ const chainId = await ethAdapter.getChainId();
+ console.log(`ChainId: ${chainId}`);
+
+ // const safeVersion = '1.3.0'
+ // const isL1SafeMasterCopy = false
+ const gnosisSafe = await Safe.create({ ethAdapter: ethAdapter, safeAddress: deployedSafeAddress });
+
+ // const safeAccountConfig = {
+ // threshold: 1, // Setting the Threshold to 1
+ // owners: SAFE_OWNERS?.split(","),
+ // };
+
+ // console.log("Deploying Security Council safe..");
+
+ // const gnosisSafe = await safeFactory.deploySafe({ safeAccountConfig });
+ const safeAddress = await gnosisSafe.getAddress();
+
+ console.log(`Security Council Safe deployed at: ${safeAddress}`);
+
+ // Deploy Zodiac
+ const deployZodiac = deployAndSetUpModule(
+ KnownContracts.ROLES,
+ {
+ types: ["address", "address", "address"],
+ values: [safeAddress, safeAddress, safeAddress],
+ },
+ provider,
+ chainId,
+ (await provider.getTransactionCount(await signer.getAddress())).toString(),
+ );
+
+ console.log("deployZodiac.expectedModuleAddress :", deployZodiac.expectedModuleAddress);
+
+ const tx = await signer.sendTransaction(deployZodiac.transaction);
+ await tx.wait();
+ console.log("deployZodiac SendTransaction");
+
+ console.log("checking to see if module is enabled");
+ const isEnabled = await gnosisSafe.isModuleEnabled(deployZodiac.expectedModuleAddress);
+ console.log("Is module enabled? :", isEnabled);
+
+ const enableModuleTx = await gnosisSafe.createEnableModuleTx(deployZodiac.expectedModuleAddress);
+
+ console.log("User connecting to Safe");
+
+ console.log("Connect with ethAdapter & Safe address ");
+ const userSafeConnection = await gnosisSafe.connect({ ethAdapter: ethAdapter, safeAddress });
+
+ console.log("Getting enable module transaction hash");
+ const enableModuleTxHash = await userSafeConnection.getTransactionHash(enableModuleTx);
+
+ console.log("Signing...");
+ const enableModuleTxResponse = await userSafeConnection.approveTransactionHash(enableModuleTxHash);
+
+ await enableModuleTxResponse.transactionResponse?.wait();
+ console.log("Signed enable module transaction");
+
+ console.log("Executing enable module transaction");
+ const executeTxResponse = await userSafeConnection.executeTransaction(enableModuleTx);
+
+ await executeTxResponse.transactionResponse?.wait();
+
+ console.log("Verifying module enabled");
+ const isEnabled2 = await gnosisSafe.isModuleEnabled(deployZodiac.expectedModuleAddress);
+ console.log("Is module enabled? :", isEnabled2);
+
+ // Multisend
+ console.log("Getting multisend address");
+ const multisendAddress = gnosisSafe.getMultiSendAddress();
+
+ console.log("Creating transaction data payload");
+ const data = ethers.utils.hexConcat([
+ "0x8b95eccd",
+ ethers.utils.defaultAbiCoder.encode(["address"], [multisendAddress]),
+ ]);
+
+ const safeTransactionData: SafeTransactionDataPartial = {
+ to: deployZodiac.expectedModuleAddress,
+ value: "0",
+ data: data,
+ safeTxGas: "100000", // required for setting the multiSend address
+ };
+
+ console.log("Crafting setMultisend transaction");
+
+ const setMultisendTx = await gnosisSafe.createTransaction({ safeTransactionData });
+
+ console.log("Getting setMultisend transaction hash");
+ const setMultisendTxHash = await userSafeConnection.getTransactionHash(setMultisendTx);
+
+ console.log("Signing setMultisend transaction hash");
+ await userSafeConnection.signTransactionHash(setMultisendTxHash);
+
+ console.log("Executing setMultisend transaction");
+ const executeSendMultisendTxResponse = await userSafeConnection.executeTransaction(setMultisendTx);
+ await executeSendMultisendTxResponse.transactionResponse?.wait();
+
+ console.log("Zodiac Role Creation Complete!");
+
+ // Create Role 1
+ console.log("Creating Zodiac Role 1");
+
+ // AssignRoles
+ console.log("Creating assignRoles 1 transaction data payload");
+ const assignRolesData = ethers.utils.hexConcat([
+ "0xa6edf38f", // 0xa6edf38f = assignRoles in Roles
+ ethers.utils.defaultAbiCoder.encode(["address", "uint16[]", "bool[]"], [safe4OutOf8Contract, [1], [true]]),
+ ]);
+
+ console.log("Crafting createAssignRoles 1 transaction");
+ const safeTransactionData2: SafeTransactionDataPartial = {
+ to: deployZodiac.expectedModuleAddress,
+ value: "0",
+ data: assignRolesData,
+ };
+
+ const createAssignRolesTx = await gnosisSafe.createTransaction({ safeTransactionData: safeTransactionData2 });
+ console.log("createAssignRoles transaction created");
+
+ console.log("Getting createAssignRoles 1 transaction hash");
+ const getAssignRolesTxHash = await userSafeConnection.getTransactionHash(createAssignRolesTx);
+
+ console.log("Signing setMultisend 1 transaction hash");
+ await userSafeConnection.signTransactionHash(getAssignRolesTxHash);
+
+ console.log("Executing createAssignRoles 1 transaction");
+ const executecreateAssignRolesTxResponse = await userSafeConnection.executeTransaction(createAssignRolesTx);
+ await executecreateAssignRolesTxResponse.transactionResponse?.wait();
+
+ // ScopeTarget
+ console.log("Creating ScopeTarget transaction data payload");
+ const scopeTargetData = ethers.utils.hexConcat([
+ "0x5e826695", // 0x5e826695 = scopeTarget function in Roles
+ ethers.utils.defaultAbiCoder.encode(["uint16", "address"], [1, timelockContract]),
+ ]);
+
+ const safeTransactionData3: SafeTransactionDataPartial = {
+ to: deployZodiac.expectedModuleAddress,
+ value: "0",
+ data: scopeTargetData,
+ };
+
+ const createScopeTargetTx = await gnosisSafe.createTransaction({ safeTransactionData: safeTransactionData3 });
+ console.log("Created createScopeTarget transaction");
+
+ console.log("Getting createScopeTarget transaction hash");
+ const getScopeTargetTxHash = await userSafeConnection.getTransactionHash(createScopeTargetTx);
+
+ console.log("Signing getScopeTarget transaction hash");
+ await userSafeConnection.signTransactionHash(getScopeTargetTxHash);
+
+ console.log("Executing createScopeTargetTx transaction");
+ const executeCreateScopeTargetTxResponse = await userSafeConnection.executeTransaction(createScopeTargetTx);
+ await executeCreateScopeTargetTxResponse.transactionResponse?.wait();
+
+ // ScopeFunction - Schedule Function with 7 day delay
+
+ console.log("Creating scopeFunction (schedule w/ 7days delay) transaction data payload");
+ const scopeFunctionData = ethers.utils.hexConcat([
+ "0x33a0480c", // 0x33a0480c = scopeFunction in Roles
+ ethers.utils.defaultAbiCoder.encode(
+ ["uint16", "address", "bytes4", "bool[]", "uint8[]", "uint8[]", "bytes[]", "uint8"],
+ [
+ 1,
+ timelockContract,
+ "0x01d5062a", // 0x01d5062a = schedule function in Timelock
+ [false, false, false, false, false, true],
+ [0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0],
+ ["0x", "0x", "0x", "0x", "0x", ethers.utils.defaultAbiCoder.encode(["uint256"], [604800])],
+ 0,
+ ],
+ ),
+ ]);
+
+ console.log("Crafting scopeFunctionData (schedule w/ 7days delay) transaction");
+ const safeTransactionData4: SafeTransactionDataPartial = {
+ to: deployZodiac.expectedModuleAddress,
+ value: "0",
+ data: scopeFunctionData,
+ };
+
+ const createScopeFunctionTx = await gnosisSafe.createTransaction({ safeTransactionData: safeTransactionData4 });
+ console.log("Created createScopeFunction (schedule w/ 7days delay) transaction");
+
+ console.log("Getting createScopeFunction (schedule w/ 7days delay) transaction hash");
+ const getCreateScopeFunctionTxHash = await userSafeConnection.getTransactionHash(createScopeFunctionTx);
+
+ console.log("Signing getCreateScopeFunction (schedule w/ 7days delay) transaction hash");
+ await userSafeConnection.signTransactionHash(getCreateScopeFunctionTxHash);
+
+ console.log("Executing createScopeAllow (schedule w/ 7days delay) transaction");
+ const executeCreateScopeFunctionTxResponse = await userSafeConnection.executeTransaction(createScopeFunctionTx);
+ await executeCreateScopeFunctionTxResponse.transactionResponse?.wait();
+
+ // ScopeAllow - Execute Function
+
+ console.log("Creating scopeAllow (execute) transaction data payload");
+ const scopeAllow10 = ethers.utils.hexConcat([
+ "0x2fcf52d1", // 0x2fcf52d1 = scopeAllow function in Roles
+ ethers.utils.defaultAbiCoder.encode(
+ ["uint16", "address", "bytes4", "uint8"],
+ [1, timelockContract, "0x134008d3", 0], // 0x134008d3 = execute function in Timelock
+ ),
+ ]);
+ // console.log("scopeAllowData3 :", scopeAllowData3);
+
+ console.log("Crafting createScopeAllow (execute) transaction");
+ const safeTransactionData10: SafeTransactionDataPartial = {
+ to: deployZodiac.expectedModuleAddress,
+ value: "0",
+ data: scopeAllow10,
+ };
+
+ const createScopeAllowTx10 = await gnosisSafe.createTransaction({ safeTransactionData: safeTransactionData10 });
+ console.log("Created createScopeAllow (execute) transaction");
+
+ console.log("Getting createScopeAllow (execute) transaction hash");
+ const getScopeAllowTxTxHash10 = await userSafeConnection.getTransactionHash(createScopeAllowTx10);
+
+ console.log("Signing getScopeAllow (execute) transaction hash");
+ await userSafeConnection.signTransactionHash(getScopeAllowTxTxHash10);
+
+ console.log("Executing createScopeAllow (execute) transaction");
+ const executeCreateScopeAllowTxResponse10 = await userSafeConnection.executeTransaction(createScopeAllowTx10);
+ await executeCreateScopeAllowTxResponse10.transactionResponse?.wait();
+
+ // Create Role 2
+ console.log("Creating Zodiac Role 2");
+
+ // AssignRoles
+ console.log("Creating assignRoles 2 transaction data payload");
+ const assignRolesData21 = ethers.utils.hexConcat([
+ "0xa6edf38f", // 0xa6edf38f = assignRoles in Roles
+ ethers.utils.defaultAbiCoder.encode(["address", "uint16[]", "bool[]"], [safe5OutOf8Contract, [2], [true]]),
+ ]);
+
+ console.log("Crafting createAssignRoles 2 transaction");
+ const safeTransactionData21: SafeTransactionDataPartial = {
+ to: deployZodiac.expectedModuleAddress,
+ value: "0",
+ data: assignRolesData21,
+ };
+
+ const createAssignRolesTx21 = await gnosisSafe.createTransaction({ safeTransactionData: safeTransactionData21 });
+ console.log("createAssignRoles transaction created");
+
+ console.log("Getting createAssignRoles 2 transaction hash");
+ const getAssignRolesTxHash21 = await userSafeConnection.getTransactionHash(createAssignRolesTx21);
+
+ console.log("Signing setMultisend 2 transaction hash");
+ await userSafeConnection.signTransactionHash(getAssignRolesTxHash21);
+
+ console.log("Executing createAssignRoles 2 transaction");
+ const executecreateAssignRolesTxResponse21 = await userSafeConnection.executeTransaction(createAssignRolesTx21);
+ await executecreateAssignRolesTxResponse21.transactionResponse?.wait();
+
+ // ScopeTarget
+ console.log("Creating ScopeTarget transaction data payload");
+ const scopeTargetData5 = ethers.utils.hexConcat([
+ "0x5e826695", // 0x5e826695 = scopeTarget function in Roles
+ ethers.utils.defaultAbiCoder.encode(["uint16", "address"], [2, timelockContract]),
+ ]);
+
+ const safeTransactionData5: SafeTransactionDataPartial = {
+ to: deployZodiac.expectedModuleAddress,
+ value: "0",
+ data: scopeTargetData5,
+ };
+
+ const createScopeTargetTx5 = await gnosisSafe.createTransaction({ safeTransactionData: safeTransactionData5 });
+ console.log("Created createScopeTarget transaction");
+
+ console.log("Getting createScopeTarget transaction hash");
+ const getScopeTargetTxHash5 = await userSafeConnection.getTransactionHash(createScopeTargetTx5);
+
+ console.log("Signing getScopeTarget transaction hash");
+ await userSafeConnection.signTransactionHash(getScopeTargetTxHash5);
+
+ console.log("Executing createScopeTargetTx transaction");
+ const executeCreateScopeTargetTxResponse5 = await userSafeConnection.executeTransaction(createScopeTargetTx5);
+ await executeCreateScopeTargetTxResponse5.transactionResponse?.wait();
+
+ // ScopeFunction - Schedule Function with 1 day delay
+
+ console.log("Creating scopeFunction (schedule w/ 1 day delay) transaction data payload");
+ const scopeFunctionData25 = ethers.utils.hexConcat([
+ "0x33a0480c", // 0x33a0480c = ScopeFunction in Roles
+ ethers.utils.defaultAbiCoder.encode(
+ ["uint16", "address", "bytes4", "bool[]", "uint8[]", "uint8[]", "bytes[]", "uint8"],
+ [
+ 2,
+ timelockContract,
+ "0x01d5062a", // 0x01d5062a = schedule function in Timelock
+ [false, false, false, false, false, true],
+ [0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0],
+ ["0x", "0x", "0x", "0x", "0x", ethers.utils.defaultAbiCoder.encode(["uint256"], [86400])],
+ 0,
+ ],
+ ),
+ ]);
+
+ console.log("Crafting scopeFunctionData (schedule w/ 1 day delay) transaction");
+ const safeTransactionData25: SafeTransactionDataPartial = {
+ to: deployZodiac.expectedModuleAddress,
+ value: "0",
+ data: scopeFunctionData25,
+ };
+
+ const createScopeFunctionTx25 = await gnosisSafe.createTransaction({ safeTransactionData: safeTransactionData25 });
+ console.log("Created createScopeFunction (schedule w/ 1 day delay) transaction");
+
+ console.log("Getting createScopeFunction (schedule w/ 1 day delay) transaction hash");
+ const getCreateScopeFunctionTxHash25 = await userSafeConnection.getTransactionHash(createScopeFunctionTx25);
+
+ console.log("Signing getCreateScopeFunction (schedule w/ 1 day delay) transaction hash");
+ await userSafeConnection.signTransactionHash(getCreateScopeFunctionTxHash25);
+
+ console.log("Executing createScopeAllow (schedule w/ 1 day delay) transaction");
+ const executeCreateScopeFunctionTxResponse25 = await userSafeConnection.executeTransaction(createScopeFunctionTx25);
+ await executeCreateScopeFunctionTxResponse25.transactionResponse?.wait();
+
+ // ScopeAllow - Execute Function
+
+ console.log("Creating scopeAllow (execute) transaction data payload");
+ const scopeAllow30 = ethers.utils.hexConcat([
+ "0x2fcf52d1", // 0x2fcf52d1 = scopeAllow function in Roles
+ ethers.utils.defaultAbiCoder.encode(
+ ["uint16", "address", "bytes4", "uint8"],
+ [2, timelockContract, "0x134008d3", 0], // 0x134008d3 = execute function in Timelock
+ ),
+ ]);
+
+ console.log("Crafting createScopeAllow (execute) transaction");
+ const safeTransactionData30: SafeTransactionDataPartial = {
+ to: deployZodiac.expectedModuleAddress,
+ value: "0",
+ data: scopeAllow30,
+ };
+
+ const createScopeAllowTx30 = await gnosisSafe.createTransaction({ safeTransactionData: safeTransactionData30 });
+ console.log("Created createScopeAllow (execute) transaction");
+
+ console.log("Getting createScopeAllow (execute) transaction hash");
+ const getScopeAllowTxTxHash30 = await userSafeConnection.getTransactionHash(createScopeAllowTx30);
+
+ console.log("Signing getScopeAllow (execute) transaction hash");
+ await userSafeConnection.signTransactionHash(getScopeAllowTxTxHash30);
+
+ console.log("Executing createScopeAllow (execute) transaction");
+ const executeCreateScopeAllowTxResponse30 = await userSafeConnection.executeTransaction(createScopeAllowTx30);
+ await executeCreateScopeAllowTxResponse30.transactionResponse?.wait();
+
+ // Creating Role 3
+ console.log("Creating Zodiac Role 3");
+
+ // AssignRoles 3
+ console.log("Creating assignRoles 3 transaction data payload");
+ const assignRolesData35 = ethers.utils.hexConcat([
+ "0xa6edf38f", // 0xa6edf38f = assignRoles function in Roles
+ ethers.utils.defaultAbiCoder.encode(["address", "uint16[]", "bool[]"], [safe4OutOf8Contract, [3], [true]]),
+ ]);
+
+ console.log("Crafting createAssignRoles 3 transaction");
+ const safeTransactionData35: SafeTransactionDataPartial = {
+ to: deployZodiac.expectedModuleAddress,
+ value: "0",
+ data: assignRolesData35,
+ };
+
+ const createAssignRolesTx35 = await gnosisSafe.createTransaction({ safeTransactionData: safeTransactionData35 });
+ console.log("Created createAssignRoles 3 transaction");
+
+ console.log("Getting createAssignRoles 3 transaction hash");
+ const getAssignRolesTxHash35 = await userSafeConnection.getTransactionHash(createAssignRolesTx35);
+
+ console.log("Signing setMultisend transaction hash");
+ await userSafeConnection.signTransactionHash(getAssignRolesTxHash35);
+
+ console.log("Executing createAssignRoles 3 transaction");
+ const executecreateAssignRolesTxResponse35 = await userSafeConnection.executeTransaction(createAssignRolesTx35);
+ await executecreateAssignRolesTxResponse35.transactionResponse?.wait();
+
+ // ScopeTarget 3
+
+ console.log("Creating ScopeTarget 3 transaction data payload");
+ const ScopeTargetData40 = ethers.utils.hexConcat([
+ "0x5e826695", // 0x5e826695 = ScopeTarget function in Roles
+ ethers.utils.defaultAbiCoder.encode(["uint16", "address"], [3, proxyContract]),
+ ]);
+
+ const safeTransactionData40: SafeTransactionDataPartial = {
+ to: deployZodiac.expectedModuleAddress,
+ value: "0",
+ data: ScopeTargetData40,
+ };
+
+ const createAllowTargetTx40 = await gnosisSafe.createTransaction({ safeTransactionData: safeTransactionData40 });
+ console.log("Created createAllowTarget 3 transaction");
+
+ console.log("Getting createAllowTarget 3 transaction hash");
+ const getAllowTargetTxHash40 = await userSafeConnection.getTransactionHash(createAllowTargetTx40);
+
+ console.log("Signing getAllowTarget 3 transaction hash");
+ await userSafeConnection.signTransactionHash(getAllowTargetTxHash40);
+
+ console.log("Executing createAllowTarget 3 transaction");
+ const executeCreateAllowTargetTxResponse40 = await userSafeConnection.executeTransaction(createAllowTargetTx40);
+ await executeCreateAllowTargetTxResponse40.transactionResponse?.wait();
+
+ // ScopeAllowFunction 3 - pauseByType
+ console.log("Creating scopeAllow (pauseByTime) transaction data payload");
+ const scopeAllowData45 = ethers.utils.hexConcat([
+ "0x2fcf52d1",
+ ethers.utils.defaultAbiCoder.encode(["uint16", "address", "bytes4", "uint8"], [3, proxyContract, "0x8264bd82", 0]), // 0x8264bd82 = pauseByType function in Proxy
+ ]);
+
+ console.log("Crafting createScopeAllow (pauseByTime) transaction");
+ const safeTransactionData45: SafeTransactionDataPartial = {
+ to: deployZodiac.expectedModuleAddress,
+ value: "0",
+ data: scopeAllowData45,
+ };
+
+ const createScopeAllowTx45 = await gnosisSafe.createTransaction({ safeTransactionData: safeTransactionData45 });
+ console.log("Created createScopeAllow (pauseByTime) transaction");
+
+ console.log("Getting createScopeAllow (pauseByTime) transaction hash");
+ const getScopeAllowTxTxHash45 = await userSafeConnection.getTransactionHash(createScopeAllowTx45);
+
+ console.log("Signing getScopeAllow (pauseByTime) transaction hash");
+ await userSafeConnection.signTransactionHash(getScopeAllowTxTxHash45);
+
+ console.log("Executing createScopeAllow (pauseByTime) transaction");
+ const executeCreateScopeAllowTxResponse45 = await userSafeConnection.executeTransaction(createScopeAllowTx45);
+ await executeCreateScopeAllowTxResponse45.transactionResponse?.wait();
+
+ // ScopeFunction - grantRole Function with keccak256("OPERATOR_ROLE") condition
+
+ console.log("Creating scopeFunction transaction data payload");
+ const scopeFunctionData50 = ethers.utils.hexConcat([
+ "0x33a0480c",
+ ethers.utils.defaultAbiCoder.encode(
+ ["uint16", "address", "bytes4", "bool[]", "uint8[]", "uint8[]", "bytes[]", "uint8"],
+ [3, proxyContract, "0x2f2ff15d", [true, false], [0, 0], [0, 0], [OPERATOR_ROLE, "0x"], 0],
+ ),
+ ]);
+
+ console.log("Crafting scopeFunctionData (grantRole) transaction");
+ const safeTransactionData50: SafeTransactionDataPartial = {
+ to: deployZodiac.expectedModuleAddress,
+ value: "0",
+ data: scopeFunctionData50,
+ };
+
+ const createScopeFunctionTx50 = await gnosisSafe.createTransaction({ safeTransactionData: safeTransactionData50 });
+ console.log("Created createScopeFunction (grantRole) transaction");
+
+ console.log("Getting createScopeFunction (grantRole) transaction hash");
+ const getCreateScopeFunctionTxHash50 = await userSafeConnection.getTransactionHash(createScopeFunctionTx50);
+
+ console.log("Signing getCreateScopeFunction (grantRole) transaction hash");
+ await userSafeConnection.signTransactionHash(getCreateScopeFunctionTxHash50);
+
+ console.log("Executing createScopeAllow (grantRole) transaction");
+ const executeCreateScopeFunctionTxResponse50 = await userSafeConnection.executeTransaction(createScopeFunctionTx50);
+ await executeCreateScopeFunctionTxResponse50.transactionResponse?.wait();
+
+ // ScopeFunction - revokeRole Function with keccak256("OPERATOR_ROLE") condition
+
+ console.log("Creating scopeFunction (revokeRole) transaction data payload");
+ const scopeFunctionData55 = ethers.utils.hexConcat([
+ "0x33a0480c",
+ ethers.utils.defaultAbiCoder.encode(
+ ["uint16", "address", "bytes4", "bool[]", "uint8[]", "uint8[]", "bytes[]", "uint8"],
+ [3, proxyContract, "0xd547741f", [true, false], [0, 0], [0, 0], [OPERATOR_ROLE, "0x"], 0],
+ ),
+ ]);
+
+ console.log("Crafting scopeFunctionData (revokeRole) transaction");
+ const safeTransactionData55: SafeTransactionDataPartial = {
+ to: deployZodiac.expectedModuleAddress,
+ value: "0",
+ data: scopeFunctionData55,
+ };
+
+ const createScopeFunctionTx55 = await gnosisSafe.createTransaction({ safeTransactionData: safeTransactionData55 });
+ console.log("Created createScopeFunction (revokeRole) transaction");
+
+ console.log("Getting createScopeFunction (revokeRole) transaction hash");
+ const getCreateScopeFunctionTxHash55 = await userSafeConnection.getTransactionHash(createScopeFunctionTx55);
+
+ console.log("Signing getCreateScopeFunction (revokeRole) transaction hash");
+ await userSafeConnection.signTransactionHash(getCreateScopeFunctionTxHash55);
+
+ console.log("Executing createScopeAllow (revokeRole) transaction");
+ const executeCreateScopeFunctionTxResponse55 = await userSafeConnection.executeTransaction(createScopeFunctionTx55);
+ await executeCreateScopeFunctionTxResponse55.transactionResponse?.wait();
+};
+
+main()
+ .then(() => process.exit(0))
+ .catch((error) => {
+ console.error(error);
+ process.exit(1);
+ });
diff --git a/contracts/scripts/gnosis/createZodiacL2.ts b/contracts/scripts/gnosis/createZodiacL2.ts
new file mode 100644
index 000000000..3c441f224
--- /dev/null
+++ b/contracts/scripts/gnosis/createZodiacL2.ts
@@ -0,0 +1,837 @@
+import { ethers } from "hardhat";
+import { requireEnv } from "../hardhat/utils";
+import Safe, { EthersAdapter } from "@safe-global/protocol-kit";
+import { deployAndSetUpModule, KnownContracts } from "@gnosis.pm/zodiac";
+import { SafeTransactionDataPartial } from "@safe-global/safe-core-sdk-types";
+import { OPERATOR_ROLE, L1_L2_MESSAGE_SETTER_ROLE } from "../../test/utils/constants";
+import { get1559Fees } from "../utils";
+
+const main = async () => {
+ const RPC_URL = requireEnv("BLOCKCHAIN_NODE");
+ const SAFE_OWNER1_PRIVATE_KEY = requireEnv("SAFE_OWNER1_PRIVATE_KEY");
+ // const SAFE_OWNERS = requireEnv("SAFE_OWNERS");
+ const safe4OutOf8Contract = requireEnv("SAFE_4_OUT_OF_8_CONTRACT");
+ const safe5OutOf8Contract = requireEnv("SAFE_5_OUT_OF_8_CONTRACT");
+ const timelockContract = requireEnv("TIMELOCK_CONTRACT");
+ const proxyContract = requireEnv("PROXY_CONTRACT");
+
+ const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
+ const signer = new ethers.Wallet(SAFE_OWNER1_PRIVATE_KEY, provider);
+ const deployedSafeAddress = requireEnv("SAFE_SECURITY_COUNCIL_ADDRESS");
+
+ const ethAdapter = new EthersAdapter({ ethers, signerOrProvider: signer });
+
+ const chainId = await ethAdapter.getChainId();
+ console.log(`ChainId: ${chainId}`);
+ const eip1559Fees = await get1559Fees(provider);
+
+ // const safeVersion = '1.3.0'
+ // const isL1SafeMasterCopy = false
+ const gnosisSafe = await Safe.create({ ethAdapter: ethAdapter, safeAddress: deployedSafeAddress });
+
+ // const safeAccountConfig = {
+ // threshold: 1, // Setting the Threshold to 1
+ // owners: SAFE_OWNERS?.split(","),
+ // };
+
+ // console.log("Deploying Security Council safe..");
+
+ // const gnosisSafe = await safeFactory.deploySafe({ safeAccountConfig });
+ const safeAddress = await gnosisSafe.getAddress();
+
+ console.log(`Security Council Safe deployed at: ${safeAddress}`);
+
+ // Deploy Zodiac
+ const deployZodiac = deployAndSetUpModule(
+ KnownContracts.ROLES,
+ {
+ types: ["address", "address", "address"],
+ values: [safeAddress, safeAddress, safeAddress],
+ },
+ provider,
+ 5, // Can change into 'chainId' once the network is supported in https://github.com/gnosis/zodiac/blob/master/sdk/contracts.ts
+ (await provider.getTransactionCount(await signer.getAddress())).toString(),
+ );
+
+ console.log("deployZodiac.expectedModuleAddress :", deployZodiac.expectedModuleAddress);
+
+ const tx = await signer.sendTransaction({ ...deployZodiac.transaction, ...(await get1559Fees(provider)) });
+ await tx.wait();
+ console.log("deployZodiac SendTransaction");
+
+ console.log("checking to see if module is enabled");
+ const isEnabled = await gnosisSafe.isModuleEnabled(deployZodiac.expectedModuleAddress);
+ console.log("Is module enabled? :", isEnabled);
+
+ const enableModuleTx = await gnosisSafe.createEnableModuleTx(deployZodiac.expectedModuleAddress);
+
+ console.log("User connecting to Safe");
+
+ console.log("Connect with ethAdapter & Safe address ");
+ const userSafeConnection = await gnosisSafe.connect({ ethAdapter: ethAdapter, safeAddress });
+
+ console.log("Getting enable module transaction hash");
+ const enableModuleTxHash = await userSafeConnection.getTransactionHash(enableModuleTx);
+
+ console.log("Signing...");
+ const enableModuleTxResponse = await userSafeConnection.approveTransactionHash(enableModuleTxHash, {
+ maxFeePerGas: eip1559Fees.maxFeePerGas?.toString(),
+ maxPriorityFeePerGas: eip1559Fees.maxPriorityFeePerGas?.toString(),
+ });
+
+ await enableModuleTxResponse.transactionResponse?.wait();
+ console.log("Signed enable module transaction");
+
+ console.log("Executing enable module transaction");
+ const executeTxResponse = await userSafeConnection.executeTransaction(enableModuleTx, {
+ maxFeePerGas: eip1559Fees.maxFeePerGas?.toString(),
+ maxPriorityFeePerGas: eip1559Fees.maxPriorityFeePerGas?.toString(),
+ });
+
+ await executeTxResponse.transactionResponse?.wait();
+
+ console.log("Verifying module enabled");
+ const isEnabled2 = await gnosisSafe.isModuleEnabled(deployZodiac.expectedModuleAddress);
+ console.log("Is module enabled? :", isEnabled2);
+
+ // Multisend
+ console.log("Getting multisend address");
+ const multisendAddress = gnosisSafe.getMultiSendAddress();
+
+ console.log("Creating transaction data payload");
+ const data = ethers.utils.hexConcat([
+ "0x8b95eccd",
+ ethers.utils.defaultAbiCoder.encode(["address"], [multisendAddress]),
+ ]);
+
+ const safeTransactionData: SafeTransactionDataPartial = {
+ to: deployZodiac.expectedModuleAddress,
+ value: "0",
+ data: data,
+ safeTxGas: "100000", // required for setting the multiSend address
+ };
+
+ console.log("Crafting setMultisend transaction");
+
+ const setMultisendTx = await gnosisSafe.createTransaction({ safeTransactionData });
+
+ console.log("Getting setMultisend transaction hash");
+ const setMultisendTxHash = await userSafeConnection.getTransactionHash(setMultisendTx);
+
+ console.log("Signing setMultisend transaction hash");
+ await userSafeConnection.signTransactionHash(setMultisendTxHash);
+
+ console.log("Executing setMultisend transaction");
+ const executeSendMultisendTxResponse = await userSafeConnection.executeTransaction(setMultisendTx, {
+ maxFeePerGas: eip1559Fees.maxFeePerGas?.toString(),
+ maxPriorityFeePerGas: eip1559Fees.maxPriorityFeePerGas?.toString(),
+ });
+ await executeSendMultisendTxResponse.transactionResponse?.wait();
+
+ console.log("Zodiac Role Creation Complete!");
+
+ // Create Role 1
+ console.log("Creating Zodiac Role 1");
+
+ // AssignRoles
+ console.log("Creating assignRoles 1 transaction data payload");
+ const assignRolesData = ethers.utils.hexConcat([
+ "0xa6edf38f", // 0xa6edf38f = assignRoles in Roles
+ ethers.utils.defaultAbiCoder.encode(["address", "uint16[]", "bool[]"], [safe4OutOf8Contract, [1], [true]]),
+ ]);
+
+ console.log("Crafting createAssignRoles 1 transaction");
+ const safeTransactionData2: SafeTransactionDataPartial = {
+ to: deployZodiac.expectedModuleAddress,
+ value: "0",
+ data: assignRolesData,
+ };
+
+ const createAssignRolesTx = await gnosisSafe.createTransaction({ safeTransactionData: safeTransactionData2 });
+ console.log("createAssignRoles transaction created");
+
+ console.log("Getting createAssignRoles 1 transaction hash");
+ const getAssignRolesTxHash = await userSafeConnection.getTransactionHash(createAssignRolesTx);
+
+ console.log("Signing setMultisend 1 transaction hash");
+ await userSafeConnection.signTransactionHash(getAssignRolesTxHash);
+
+ console.log("Executing createAssignRoles 1 transaction");
+ const executecreateAssignRolesTxResponse = await userSafeConnection.executeTransaction(createAssignRolesTx, {
+ maxFeePerGas: eip1559Fees.maxFeePerGas?.toString(),
+ maxPriorityFeePerGas: eip1559Fees.maxPriorityFeePerGas?.toString(),
+ });
+ await executecreateAssignRolesTxResponse.transactionResponse?.wait();
+
+ // ScopeTarget
+ console.log("Creating ScopeTarget transaction data payload");
+ const scopeTargetData = ethers.utils.hexConcat([
+ "0x5e826695", // 0x5e826695 = scopeTarget function in Roles
+ ethers.utils.defaultAbiCoder.encode(["uint16", "address"], [1, timelockContract]),
+ ]);
+
+ const safeTransactionData3: SafeTransactionDataPartial = {
+ to: deployZodiac.expectedModuleAddress,
+ value: "0",
+ data: scopeTargetData,
+ };
+
+ const createScopeTargetTx = await gnosisSafe.createTransaction({ safeTransactionData: safeTransactionData3 });
+ console.log("Created createScopeTarget transaction");
+
+ console.log("Getting createScopeTarget transaction hash");
+ const getScopeTargetTxHash = await userSafeConnection.getTransactionHash(createScopeTargetTx);
+
+ console.log("Signing getScopeTarget transaction hash");
+ await userSafeConnection.signTransactionHash(getScopeTargetTxHash);
+
+ console.log("Executing createScopeTargetTx transaction");
+ const executeCreateScopeTargetTxResponse = await userSafeConnection.executeTransaction(createScopeTargetTx, {
+ maxFeePerGas: eip1559Fees.maxFeePerGas?.toString(),
+ maxPriorityFeePerGas: eip1559Fees.maxPriorityFeePerGas?.toString(),
+ });
+ await executeCreateScopeTargetTxResponse.transactionResponse?.wait();
+
+ // ScopeFunction - Schedule Function with 7 day delay
+
+ console.log("Creating scopeFunction (schedule w/ 7days delay) transaction data payload");
+ const scopeFunctionData = ethers.utils.hexConcat([
+ "0x33a0480c", // 0x33a0480c = scopeFunction in Roles
+ ethers.utils.defaultAbiCoder.encode(
+ ["uint16", "address", "bytes4", "bool[]", "uint8[]", "uint8[]", "bytes[]", "uint8"],
+ [
+ 1,
+ timelockContract,
+ "0x01d5062a", // 0x01d5062a = schedule function in Timelock
+ [false, false, false, false, false, true],
+ [0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0],
+ ["0x", "0x", "0x", "0x", "0x", ethers.utils.defaultAbiCoder.encode(["uint256"], [604800])],
+ 0,
+ ],
+ ),
+ ]);
+
+ console.log("Crafting scopeFunctionData (schedule w/ 7days delay) transaction");
+ const safeTransactionData4: SafeTransactionDataPartial = {
+ to: deployZodiac.expectedModuleAddress,
+ value: "0",
+ data: scopeFunctionData,
+ };
+
+ const createScopeFunctionTx = await gnosisSafe.createTransaction({ safeTransactionData: safeTransactionData4 });
+ console.log("Created createScopeFunction (schedule w/ 7days delay) transaction");
+
+ console.log("Getting createScopeFunction (schedule w/ 7days delay) transaction hash");
+ const getCreateScopeFunctionTxHash = await userSafeConnection.getTransactionHash(createScopeFunctionTx);
+
+ console.log("Signing getCreateScopeFunction (schedule w/ 7days delay) transaction hash");
+ await userSafeConnection.signTransactionHash(getCreateScopeFunctionTxHash);
+
+ console.log("Executing createScopeAllow (schedule w/ 7days delay) transaction");
+ const executeCreateScopeFunctionTxResponse = await userSafeConnection.executeTransaction(createScopeFunctionTx, {
+ maxFeePerGas: eip1559Fees.maxFeePerGas?.toString(),
+ maxPriorityFeePerGas: eip1559Fees.maxPriorityFeePerGas?.toString(),
+ });
+ await executeCreateScopeFunctionTxResponse.transactionResponse?.wait();
+
+ // ScopeAllow - Execute Function
+
+ console.log("Creating scopeAllow (execute) transaction data payload");
+ const scopeAllow10 = ethers.utils.hexConcat([
+ "0x2fcf52d1", // 0x2fcf52d1 = scopeAllow function in Roles
+ ethers.utils.defaultAbiCoder.encode(
+ ["uint16", "address", "bytes4", "uint8"],
+ [1, timelockContract, "0x134008d3", 0], // 0x134008d3 = execute function in Timelock
+ ),
+ ]);
+ // console.log("scopeAllowData3 :", scopeAllowData3);
+
+ console.log("Crafting createScopeAllow (execute) transaction");
+ const safeTransactionData10: SafeTransactionDataPartial = {
+ to: deployZodiac.expectedModuleAddress,
+ value: "0",
+ data: scopeAllow10,
+ };
+
+ const createScopeAllowTx10 = await gnosisSafe.createTransaction({ safeTransactionData: safeTransactionData10 });
+ console.log("Created createScopeAllow (execute) transaction");
+
+ console.log("Getting createScopeAllow (execute) transaction hash");
+ const getScopeAllowTxTxHash10 = await userSafeConnection.getTransactionHash(createScopeAllowTx10);
+
+ console.log("Signing getScopeAllow (execute) transaction hash");
+ await userSafeConnection.signTransactionHash(getScopeAllowTxTxHash10);
+
+ console.log("Executing createScopeAllow (execute) transaction");
+ const executeCreateScopeAllowTxResponse10 = await userSafeConnection.executeTransaction(createScopeAllowTx10, {
+ maxFeePerGas: eip1559Fees.maxFeePerGas?.toString(),
+ maxPriorityFeePerGas: eip1559Fees.maxPriorityFeePerGas?.toString(),
+ });
+ await executeCreateScopeAllowTxResponse10.transactionResponse?.wait();
+
+ // Create Role 2
+ console.log("Creating Zodiac Role 2");
+
+ // AssignRoles
+ console.log("Creating assignRoles 2 transaction data payload");
+ const assignRolesData21 = ethers.utils.hexConcat([
+ "0xa6edf38f", // 0xa6edf38f = assignRoles in Roles
+ ethers.utils.defaultAbiCoder.encode(["address", "uint16[]", "bool[]"], [safe5OutOf8Contract, [2], [true]]),
+ ]);
+
+ console.log("Crafting createAssignRoles 2 transaction");
+ const safeTransactionData21: SafeTransactionDataPartial = {
+ to: deployZodiac.expectedModuleAddress,
+ value: "0",
+ data: assignRolesData21,
+ };
+
+ const createAssignRolesTx21 = await gnosisSafe.createTransaction({ safeTransactionData: safeTransactionData21 });
+ console.log("createAssignRoles transaction created");
+
+ console.log("Getting createAssignRoles 2 transaction hash");
+ const getAssignRolesTxHash21 = await userSafeConnection.getTransactionHash(createAssignRolesTx21);
+
+ console.log("Signing setMultisend 2 transaction hash");
+ await userSafeConnection.signTransactionHash(getAssignRolesTxHash21);
+
+ console.log("Executing createAssignRoles 2 transaction");
+ const executecreateAssignRolesTxResponse21 = await userSafeConnection.executeTransaction(createAssignRolesTx21, {
+ maxFeePerGas: eip1559Fees.maxFeePerGas?.toString(),
+ maxPriorityFeePerGas: eip1559Fees.maxPriorityFeePerGas?.toString(),
+ });
+ await executecreateAssignRolesTxResponse21.transactionResponse?.wait();
+
+ // ScopeTarget
+ console.log("Creating ScopeTarget transaction data payload");
+ const scopeTargetData5 = ethers.utils.hexConcat([
+ "0x5e826695", // 0x5e826695 = scopeTarget function in Roles
+ ethers.utils.defaultAbiCoder.encode(["uint16", "address"], [2, timelockContract]),
+ ]);
+
+ const safeTransactionData5: SafeTransactionDataPartial = {
+ to: deployZodiac.expectedModuleAddress,
+ value: "0",
+ data: scopeTargetData5,
+ };
+
+ const createScopeTargetTx5 = await gnosisSafe.createTransaction({ safeTransactionData: safeTransactionData5 });
+ console.log("Created createScopeTarget transaction");
+
+ console.log("Getting createScopeTarget transaction hash");
+ const getScopeTargetTxHash5 = await userSafeConnection.getTransactionHash(createScopeTargetTx5);
+
+ console.log("Signing getScopeTarget transaction hash");
+ await userSafeConnection.signTransactionHash(getScopeTargetTxHash5);
+
+ console.log("Executing createScopeTargetTx transaction");
+ const executeCreateScopeTargetTxResponse5 = await userSafeConnection.executeTransaction(createScopeTargetTx5, {
+ maxFeePerGas: eip1559Fees.maxFeePerGas?.toString(),
+ maxPriorityFeePerGas: eip1559Fees.maxPriorityFeePerGas?.toString(),
+ });
+ await executeCreateScopeTargetTxResponse5.transactionResponse?.wait();
+
+ // ScopeFunction - Schedule Function with 1 day delay
+
+ console.log("Creating scopeFunction (schedule w/ 1 day delay) transaction data payload");
+ const scopeFunctionData25 = ethers.utils.hexConcat([
+ "0x33a0480c", // 0x33a0480c = ScopeFunction in Roles
+ ethers.utils.defaultAbiCoder.encode(
+ ["uint16", "address", "bytes4", "bool[]", "uint8[]", "uint8[]", "bytes[]", "uint8"],
+ [
+ 2,
+ timelockContract,
+ "0x01d5062a", // 0x01d5062a = schedule function in Timelock
+ [false, false, false, false, false, true],
+ [0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0],
+ ["0x", "0x", "0x", "0x", "0x", ethers.utils.defaultAbiCoder.encode(["uint256"], [86400])],
+ 0,
+ ],
+ ),
+ ]);
+
+ console.log("Crafting scopeFunctionData (schedule w/ 1 day delay) transaction");
+ const safeTransactionData25: SafeTransactionDataPartial = {
+ to: deployZodiac.expectedModuleAddress,
+ value: "0",
+ data: scopeFunctionData25,
+ };
+
+ const createScopeFunctionTx25 = await gnosisSafe.createTransaction({ safeTransactionData: safeTransactionData25 });
+ console.log("Created createScopeFunction (schedule w/ 1 day delay) transaction");
+
+ console.log("Getting createScopeFunction (schedule w/ 1 day delay) transaction hash");
+ const getCreateScopeFunctionTxHash25 = await userSafeConnection.getTransactionHash(createScopeFunctionTx25);
+
+ console.log("Signing getCreateScopeFunction (schedule w/ 1 day delay) transaction hash");
+ await userSafeConnection.signTransactionHash(getCreateScopeFunctionTxHash25);
+
+ console.log("Executing createScopeAllow (schedule w/ 1 day delay) transaction");
+ const executeCreateScopeFunctionTxResponse25 = await userSafeConnection.executeTransaction(createScopeFunctionTx25, {
+ maxFeePerGas: eip1559Fees.maxFeePerGas?.toString(),
+ maxPriorityFeePerGas: eip1559Fees.maxPriorityFeePerGas?.toString(),
+ });
+ await executeCreateScopeFunctionTxResponse25.transactionResponse?.wait();
+
+ // ScopeAllow - Execute Function
+
+ console.log("Creating scopeAllow (execute) transaction data payload");
+ const scopeAllow30 = ethers.utils.hexConcat([
+ "0x2fcf52d1", // 0x2fcf52d1 = scopeAllow function in Roles
+ ethers.utils.defaultAbiCoder.encode(
+ ["uint16", "address", "bytes4", "uint8"],
+ [2, timelockContract, "0x134008d3", 0], // 0x134008d3 = execute function in Timelock
+ ),
+ ]);
+
+ console.log("Crafting createScopeAllow (execute) transaction");
+ const safeTransactionData30: SafeTransactionDataPartial = {
+ to: deployZodiac.expectedModuleAddress,
+ value: "0",
+ data: scopeAllow30,
+ };
+
+ const createScopeAllowTx30 = await gnosisSafe.createTransaction({ safeTransactionData: safeTransactionData30 });
+ console.log("Created createScopeAllow (execute) transaction");
+
+ console.log("Getting createScopeAllow (execute) transaction hash");
+ const getScopeAllowTxTxHash30 = await userSafeConnection.getTransactionHash(createScopeAllowTx30);
+
+ console.log("Signing getScopeAllow (execute) transaction hash");
+ await userSafeConnection.signTransactionHash(getScopeAllowTxTxHash30);
+
+ console.log("Executing createScopeAllow (execute) transaction");
+ const executeCreateScopeAllowTxResponse30 = await userSafeConnection.executeTransaction(createScopeAllowTx30, {
+ maxFeePerGas: eip1559Fees.maxFeePerGas?.toString(),
+ maxPriorityFeePerGas: eip1559Fees.maxPriorityFeePerGas?.toString(),
+ });
+ await executeCreateScopeAllowTxResponse30.transactionResponse?.wait();
+
+ // Creating Role 3
+ console.log("Creating Zodiac Role 3");
+
+ // AssignRoles 3
+ console.log("Creating assignRoles 3 transaction data payload");
+ const assignRolesData35 = ethers.utils.hexConcat([
+ "0xa6edf38f", // 0xa6edf38f = assignRoles function in Roles
+ ethers.utils.defaultAbiCoder.encode(["address", "uint16[]", "bool[]"], [safe4OutOf8Contract, [3], [true]]),
+ ]);
+
+ console.log("Crafting createAssignRoles 3 transaction");
+ const safeTransactionData35: SafeTransactionDataPartial = {
+ to: deployZodiac.expectedModuleAddress,
+ value: "0",
+ data: assignRolesData35,
+ };
+
+ const createAssignRolesTx35 = await gnosisSafe.createTransaction({ safeTransactionData: safeTransactionData35 });
+ console.log("Created createAssignRoles 3 transaction");
+
+ console.log("Getting createAssignRoles 3 transaction hash");
+ const getAssignRolesTxHash35 = await userSafeConnection.getTransactionHash(createAssignRolesTx35);
+
+ console.log("Signing setMultisend transaction hash");
+ await userSafeConnection.signTransactionHash(getAssignRolesTxHash35);
+
+ console.log("Executing createAssignRoles 3 transaction");
+ const executecreateAssignRolesTxResponse35 = await userSafeConnection.executeTransaction(createAssignRolesTx35, {
+ maxFeePerGas: eip1559Fees.maxFeePerGas?.toString(),
+ maxPriorityFeePerGas: eip1559Fees.maxPriorityFeePerGas?.toString(),
+ });
+ await executecreateAssignRolesTxResponse35.transactionResponse?.wait();
+
+ // ScopeTarget 3
+
+ console.log("Creating ScopeTarget 3 transaction data payload");
+ const ScopeTargetData40 = ethers.utils.hexConcat([
+ "0x5e826695", // 0x5e826695 = ScopeTarget function in Roles
+ ethers.utils.defaultAbiCoder.encode(["uint16", "address"], [3, proxyContract]),
+ ]);
+
+ const safeTransactionData40: SafeTransactionDataPartial = {
+ to: deployZodiac.expectedModuleAddress,
+ value: "0",
+ data: ScopeTargetData40,
+ };
+
+ const createAllowTargetTx40 = await gnosisSafe.createTransaction({ safeTransactionData: safeTransactionData40 });
+ console.log("Created createAllowTarget 3 transaction");
+
+ console.log("Getting createAllowTarget 3 transaction hash");
+ const getAllowTargetTxHash40 = await userSafeConnection.getTransactionHash(createAllowTargetTx40);
+
+ console.log("Signing getAllowTarget 3 transaction hash");
+ await userSafeConnection.signTransactionHash(getAllowTargetTxHash40);
+
+ console.log("Executing createAllowTarget 3 transaction");
+ const executeCreateAllowTargetTxResponse40 = await userSafeConnection.executeTransaction(createAllowTargetTx40, {
+ maxFeePerGas: eip1559Fees.maxFeePerGas?.toString(),
+ maxPriorityFeePerGas: eip1559Fees.maxPriorityFeePerGas?.toString(),
+ });
+ await executeCreateAllowTargetTxResponse40.transactionResponse?.wait();
+
+ // ScopeAllowFunction 3 - pauseByType
+ console.log("Creating scopeAllow (pauseByTime) transaction data payload");
+ const scopeAllowData45 = ethers.utils.hexConcat([
+ "0x2fcf52d1",
+ ethers.utils.defaultAbiCoder.encode(["uint16", "address", "bytes4", "uint8"], [3, proxyContract, "0x8264bd82", 0]), // 0x8264bd82 = pauseByType function in Proxy
+ ]);
+
+ console.log("Crafting createScopeAllow (pauseByTime) transaction");
+ const safeTransactionData45: SafeTransactionDataPartial = {
+ to: deployZodiac.expectedModuleAddress,
+ value: "0",
+ data: scopeAllowData45,
+ };
+
+ const createScopeAllowTx45 = await gnosisSafe.createTransaction({ safeTransactionData: safeTransactionData45 });
+ console.log("Created createScopeAllow (pauseByTime) transaction");
+
+ console.log("Getting createScopeAllow (pauseByTime) transaction hash");
+ const getScopeAllowTxTxHash45 = await userSafeConnection.getTransactionHash(createScopeAllowTx45);
+
+ console.log("Signing getScopeAllow (pauseByTime) transaction hash");
+ await userSafeConnection.signTransactionHash(getScopeAllowTxTxHash45);
+
+ console.log("Executing createScopeAllow (pauseByTime) transaction");
+ const executeCreateScopeAllowTxResponse45 = await userSafeConnection.executeTransaction(createScopeAllowTx45, {
+ maxFeePerGas: eip1559Fees.maxFeePerGas?.toString(),
+ maxPriorityFeePerGas: eip1559Fees.maxPriorityFeePerGas?.toString(),
+ });
+ await executeCreateScopeAllowTxResponse45.transactionResponse?.wait();
+
+ // ScopeFunction - grantRole Function with keccak256("OPERATOR_ROLE") condition
+
+ console.log("Creating scopeFunction transaction data payload");
+ const scopeFunctionData50 = ethers.utils.hexConcat([
+ "0x33a0480c",
+ ethers.utils.defaultAbiCoder.encode(
+ ["uint16", "address", "bytes4", "bool[]", "uint8[]", "uint8[]", "bytes[]", "uint8"],
+ [3, proxyContract, "0x2f2ff15d", [true, false], [0, 0], [0, 0], [OPERATOR_ROLE, "0x"], 0],
+ ),
+ ]);
+
+ console.log("Crafting scopeFunctionData (grantRole) transaction");
+ const safeTransactionData50: SafeTransactionDataPartial = {
+ to: deployZodiac.expectedModuleAddress,
+ value: "0",
+ data: scopeFunctionData50,
+ };
+
+ const createScopeFunctionTx50 = await gnosisSafe.createTransaction({ safeTransactionData: safeTransactionData50 });
+ console.log("Created createScopeFunction (grantRole) transaction");
+
+ console.log("Getting createScopeFunction (grantRole) transaction hash");
+ const getCreateScopeFunctionTxHash50 = await userSafeConnection.getTransactionHash(createScopeFunctionTx50);
+
+ console.log("Signing getCreateScopeFunction (grantRole) transaction hash");
+ await userSafeConnection.signTransactionHash(getCreateScopeFunctionTxHash50);
+
+ console.log("Executing createScopeAllow (grantRole) transaction");
+ const executeCreateScopeFunctionTxResponse50 = await userSafeConnection.executeTransaction(createScopeFunctionTx50, {
+ maxFeePerGas: eip1559Fees.maxFeePerGas?.toString(),
+ maxPriorityFeePerGas: eip1559Fees.maxPriorityFeePerGas?.toString(),
+ });
+ await executeCreateScopeFunctionTxResponse50.transactionResponse?.wait();
+
+ // ScopeFunction - revokeRole Function with keccak256("OPERATOR_ROLE") condition
+
+ console.log("Creating scopeFunction (revokeRole) transaction data payload");
+ const scopeFunctionData55 = ethers.utils.hexConcat([
+ "0x33a0480c",
+ ethers.utils.defaultAbiCoder.encode(
+ ["uint16", "address", "bytes4", "bool[]", "uint8[]", "uint8[]", "bytes[]", "uint8"],
+ [3, proxyContract, "0xd547741f", [true, false], [0, 0], [0, 0], [OPERATOR_ROLE, "0x"], 0],
+ ),
+ ]);
+
+ console.log("Crafting scopeFunctionData (revokeRole) transaction");
+ const safeTransactionData55: SafeTransactionDataPartial = {
+ to: deployZodiac.expectedModuleAddress,
+ value: "0",
+ data: scopeFunctionData55,
+ };
+
+ const createScopeFunctionTx55 = await gnosisSafe.createTransaction({ safeTransactionData: safeTransactionData55 });
+ console.log("Created createScopeFunction (revokeRole) transaction");
+
+ console.log("Getting createScopeFunction (revokeRole) transaction hash");
+ const getCreateScopeFunctionTxHash55 = await userSafeConnection.getTransactionHash(createScopeFunctionTx55);
+
+ console.log("Signing getCreateScopeFunction (revokeRole) transaction hash");
+ await userSafeConnection.signTransactionHash(getCreateScopeFunctionTxHash55);
+
+ console.log("Executing createScopeAllow (revokeRole) transaction");
+ const executeCreateScopeFunctionTxResponse55 = await userSafeConnection.executeTransaction(createScopeFunctionTx55, {
+ maxFeePerGas: eip1559Fees.maxFeePerGas?.toString(),
+ maxPriorityFeePerGas: eip1559Fees.maxPriorityFeePerGas?.toString(),
+ });
+ await executeCreateScopeFunctionTxResponse55.transactionResponse?.wait();
+
+ // Creating Role 4
+ console.log("Creating Zodiac Role 4");
+
+ // AssignRoles 4
+ console.log("Creating assignRoles 4 transaction data payload");
+ const assignRolesData60 = ethers.utils.hexConcat([
+ "0xa6edf38f", // 0xa6edf38f = assignRoles function in Roles
+ ethers.utils.defaultAbiCoder.encode(["address", "uint16[]", "bool[]"], [safe4OutOf8Contract, [4], [true]]),
+ ]);
+
+ console.log("Crafting createAssignRoles 4 transaction");
+ const safeTransactionData60: SafeTransactionDataPartial = {
+ to: deployZodiac.expectedModuleAddress,
+ value: "0",
+ data: assignRolesData60,
+ };
+
+ const createAssignRolesTx60 = await gnosisSafe.createTransaction({ safeTransactionData: safeTransactionData60 });
+ console.log("Created createAssignRoles 4 transaction");
+
+ console.log("Getting createAssignRoles 4 transaction hash");
+ const getAssignRolesTxHash60 = await userSafeConnection.getTransactionHash(createAssignRolesTx60);
+
+ console.log("Signing setMultisend transaction hash");
+ await userSafeConnection.signTransactionHash(getAssignRolesTxHash60);
+
+ console.log("Executing createAssignRoles 4 transaction");
+ const executecreateAssignRolesTxResponse60 = await userSafeConnection.executeTransaction(createAssignRolesTx60, {
+ maxFeePerGas: eip1559Fees.maxFeePerGas?.toString(),
+ maxPriorityFeePerGas: eip1559Fees.maxPriorityFeePerGas?.toString(),
+ });
+ await executecreateAssignRolesTxResponse60.transactionResponse?.wait();
+
+ // ScopeTarget 4
+
+ console.log("Creating ScopeTarget 4 transaction data payload");
+ const ScopeTargetData65 = ethers.utils.hexConcat([
+ "0x5e826695", // 0x5e826695 = ScopeTarget function in Roles
+ ethers.utils.defaultAbiCoder.encode(["uint16", "address"], [4, proxyContract]),
+ ]);
+
+ const safeTransactionData65: SafeTransactionDataPartial = {
+ to: deployZodiac.expectedModuleAddress,
+ value: "0",
+ data: ScopeTargetData65,
+ };
+
+ const createAllowTargetTx65 = await gnosisSafe.createTransaction({ safeTransactionData: safeTransactionData65 });
+ console.log("Created createAllowTarget 4 transaction");
+
+ console.log("Getting createAllowTarget 4 transaction hash");
+ const getAllowTargetTxHash65 = await userSafeConnection.getTransactionHash(createAllowTargetTx65);
+
+ console.log("Signing getAllowTarget 4 transaction hash");
+ await userSafeConnection.signTransactionHash(getAllowTargetTxHash65);
+
+ console.log("Executing createAllowTarget 4 transaction");
+ const executeCreateAllowTargetTxResponse65 = await userSafeConnection.executeTransaction(createAllowTargetTx65, {
+ maxFeePerGas: eip1559Fees.maxFeePerGas?.toString(),
+ maxPriorityFeePerGas: eip1559Fees.maxPriorityFeePerGas?.toString(),
+ });
+ await executeCreateAllowTargetTxResponse65.transactionResponse?.wait();
+
+ // ScopeAllowFunction 4 - resetRateLimitAmount
+
+ console.log("Creating scopeAllow (resetRateLimitAmount) transaction data payload");
+ const scopeAllowData70 = ethers.utils.hexConcat([
+ "0x2fcf52d1",
+ ethers.utils.defaultAbiCoder.encode(["uint16", "address", "bytes4", "uint8"], [4, proxyContract, "0x557eac73", 0]), // 0x557eac73 = resetRateLimitAmount function in Proxy
+ ]);
+
+ console.log("Crafting createScopeAllow (resetRateLimitAmount) transaction");
+ const safeTransactionData70: SafeTransactionDataPartial = {
+ to: deployZodiac.expectedModuleAddress,
+ value: "0",
+ data: scopeAllowData70,
+ };
+
+ const createScopeAllowTx70 = await gnosisSafe.createTransaction({ safeTransactionData: safeTransactionData70 });
+ console.log("Created createScopeAllow (resetRateLimitAmount) transaction");
+
+ console.log("Getting createScopeAllow (resetRateLimitAmount) transaction hash");
+ const getScopeAllowTxTxHash70 = await userSafeConnection.getTransactionHash(createScopeAllowTx70);
+
+ console.log("Signing getScopeAllow (resetRateLimitAmount) transaction hash");
+ await userSafeConnection.signTransactionHash(getScopeAllowTxTxHash70);
+
+ console.log("Executing createScopeAllow (resetRateLimitAmount) transaction");
+ const executeCreateScopeAllowTxResponse70 = await userSafeConnection.executeTransaction(createScopeAllowTx70, {
+ maxFeePerGas: eip1559Fees.maxFeePerGas?.toString(),
+ maxPriorityFeePerGas: eip1559Fees.maxPriorityFeePerGas?.toString(),
+ });
+ await executeCreateScopeAllowTxResponse70.transactionResponse?.wait();
+
+ // ScopeAllowFunction 4 - setMinimumFee
+
+ console.log("Creating scopeAllow (setMinimumFee) transaction data payload");
+ const scopeAllowData75 = ethers.utils.hexConcat([
+ "0x2fcf52d1",
+ ethers.utils.defaultAbiCoder.encode(["uint16", "address", "bytes4", "uint8"], [4, proxyContract, "0x182a7506", 0]), // 0x182a7506 = setMinimumFee function in Proxy
+ ]);
+
+ console.log("Crafting createScopeAllow (setMinimumFee) transaction");
+ const safeTransactionData75: SafeTransactionDataPartial = {
+ to: deployZodiac.expectedModuleAddress,
+ value: "0",
+ data: scopeAllowData75,
+ };
+
+ const createScopeAllowTx75 = await gnosisSafe.createTransaction({ safeTransactionData: safeTransactionData75 });
+ console.log("Created createScopeAllow (setMinimumFee) transaction");
+
+ console.log("Getting createScopeAllow (setMinimumFee) transaction hash");
+ const getScopeAllowTxTxHash75 = await userSafeConnection.getTransactionHash(createScopeAllowTx75);
+
+ console.log("Signing getScopeAllow (setMinimumFee) transaction hash");
+ await userSafeConnection.signTransactionHash(getScopeAllowTxTxHash75);
+
+ console.log("Executing createScopeAllow (setMinimumFee) transaction");
+ const executeCreateScopeAllowTxResponse75 = await userSafeConnection.executeTransaction(createScopeAllowTx75, {
+ maxFeePerGas: eip1559Fees.maxFeePerGas?.toString(),
+ maxPriorityFeePerGas: eip1559Fees.maxPriorityFeePerGas?.toString(),
+ });
+ await executeCreateScopeAllowTxResponse75.transactionResponse?.wait();
+
+ // Creating Role 5
+ console.log("Creating Zodiac Role 5");
+
+ // AssignRoles 5
+ console.log("Creating assignRoles 5 transaction data payload");
+ const assignRolesData80 = ethers.utils.hexConcat([
+ "0xa6edf38f", // 0xa6edf38f = assignRoles function in Roles
+ ethers.utils.defaultAbiCoder.encode(["address", "uint16[]", "bool[]"], [safe4OutOf8Contract, [5], [true]]),
+ ]);
+
+ console.log("Crafting createAssignRoles 5 transaction");
+ const safeTransactionData80: SafeTransactionDataPartial = {
+ to: deployZodiac.expectedModuleAddress,
+ value: "0",
+ data: assignRolesData80,
+ };
+
+ const createAssignRolesTx80 = await gnosisSafe.createTransaction({ safeTransactionData: safeTransactionData80 });
+ console.log("Created createAssignRoles 5 transaction");
+
+ console.log("Getting createAssignRoles 5 transaction hash");
+ const getAssignRolesTxHash80 = await userSafeConnection.getTransactionHash(createAssignRolesTx80);
+
+ console.log("Signing setMultisend transaction hash");
+ await userSafeConnection.signTransactionHash(getAssignRolesTxHash80);
+
+ console.log("Executing createAssignRoles 5 transaction");
+ const executecreateAssignRolesTxResponse80 = await userSafeConnection.executeTransaction(createAssignRolesTx80, {
+ maxFeePerGas: eip1559Fees.maxFeePerGas?.toString(),
+ maxPriorityFeePerGas: eip1559Fees.maxPriorityFeePerGas?.toString(),
+ });
+ await executecreateAssignRolesTxResponse80.transactionResponse?.wait();
+
+ // ScopeTarget 5
+
+ console.log("Creating ScopeTarget 5 transaction data payload");
+ const ScopeTargetData85 = ethers.utils.hexConcat([
+ "0x5e826695", // 0x5e826695 = ScopeTarget function in Roles
+ ethers.utils.defaultAbiCoder.encode(["uint16", "address"], [5, proxyContract]),
+ ]);
+
+ const safeTransactionData85: SafeTransactionDataPartial = {
+ to: deployZodiac.expectedModuleAddress,
+ value: "0",
+ data: ScopeTargetData85,
+ };
+
+ const createAllowTargetTx85 = await gnosisSafe.createTransaction({ safeTransactionData: safeTransactionData85 });
+ console.log("Created createAllowTarget 5 transaction");
+
+ console.log("Getting createAllowTarget 5 transaction hash");
+ const getAllowTargetTxHash85 = await userSafeConnection.getTransactionHash(createAllowTargetTx85);
+
+ console.log("Signing getAllowTarget 5 transaction hash");
+ await userSafeConnection.signTransactionHash(getAllowTargetTxHash85);
+
+ console.log("Executing createAllowTarget 5 transaction");
+ const executeCreateAllowTargetTxResponse85 = await userSafeConnection.executeTransaction(createAllowTargetTx85, {
+ maxFeePerGas: eip1559Fees.maxFeePerGas?.toString(),
+ maxPriorityFeePerGas: eip1559Fees.maxPriorityFeePerGas?.toString(),
+ });
+ await executeCreateAllowTargetTxResponse85.transactionResponse?.wait();
+
+ // ScopeFunction - grantRole Function with keccak256("L1_L2_MESSAGE_SETTER_ROLE") condition
+
+ console.log("Creating scopeFunction transaction data payload");
+ const scopeFunctionData90 = ethers.utils.hexConcat([
+ "0x33a0480c",
+ ethers.utils.defaultAbiCoder.encode(
+ ["uint16", "address", "bytes4", "bool[]", "uint8[]", "uint8[]", "bytes[]", "uint8"],
+ [5, proxyContract, "0x2f2ff15d", [true, false], [0, 0], [0, 0], [L1_L2_MESSAGE_SETTER_ROLE, "0x"], 0],
+ ),
+ ]);
+
+ console.log("Crafting scopeFunctionData (grantRole) transaction");
+ const safeTransactionData90: SafeTransactionDataPartial = {
+ to: deployZodiac.expectedModuleAddress,
+ value: "0",
+ data: scopeFunctionData90,
+ };
+
+ const createScopeFunctionTx90 = await gnosisSafe.createTransaction({ safeTransactionData: safeTransactionData90 });
+ console.log("Created createScopeFunction (grantRole) transaction");
+
+ console.log("Getting createScopeFunction (grantRole) transaction hash");
+ const getCreateScopeFunctionTxHash90 = await userSafeConnection.getTransactionHash(createScopeFunctionTx90);
+
+ console.log("Signing getCreateScopeFunction (grantRole) transaction hash");
+ await userSafeConnection.signTransactionHash(getCreateScopeFunctionTxHash90);
+
+ console.log("Executing createScopeAllow (grantRole) transaction");
+ const executeCreateScopeFunctionTxResponse90 = await userSafeConnection.executeTransaction(createScopeFunctionTx90, {
+ maxFeePerGas: eip1559Fees.maxFeePerGas?.toString(),
+ maxPriorityFeePerGas: eip1559Fees.maxPriorityFeePerGas?.toString(),
+ });
+ await executeCreateScopeFunctionTxResponse90.transactionResponse?.wait();
+
+ // ScopeFunction - revokeRole Function with keccak256("L1_L2_MESSAGE_SETTER_ROLE") condition
+
+ console.log("Creating scopeFunction (revokeRole) transaction data payload");
+ const scopeFunctionData95 = ethers.utils.hexConcat([
+ "0x33a0480c",
+ ethers.utils.defaultAbiCoder.encode(
+ ["uint16", "address", "bytes4", "bool[]", "uint8[]", "uint8[]", "bytes[]", "uint8"],
+ [5, proxyContract, "0xd547741f", [true, false], [0, 0], [0, 0], [L1_L2_MESSAGE_SETTER_ROLE, "0x"], 0],
+ ),
+ ]);
+
+ console.log("Crafting scopeFunctionData (revokeRole) transaction");
+ const safeTransactionData95: SafeTransactionDataPartial = {
+ to: deployZodiac.expectedModuleAddress,
+ value: "0",
+ data: scopeFunctionData95,
+ };
+
+ const createScopeFunctionTx95 = await gnosisSafe.createTransaction({ safeTransactionData: safeTransactionData95 });
+ console.log("Created createScopeFunction (revokeRole) transaction");
+
+ console.log("Getting createScopeFunction (revokeRole) transaction hash");
+ const getCreateScopeFunctionTxHash95 = await userSafeConnection.getTransactionHash(createScopeFunctionTx95);
+
+ console.log("Signing getCreateScopeFunction (revokeRole) transaction hash");
+ await userSafeConnection.signTransactionHash(getCreateScopeFunctionTxHash95);
+
+ console.log("Executing createScopeAllow (revokeRole) transaction");
+ const executeCreateScopeFunctionTxResponse95 = await userSafeConnection.executeTransaction(createScopeFunctionTx95, {
+ maxFeePerGas: eip1559Fees.maxFeePerGas?.toString(),
+ maxPriorityFeePerGas: eip1559Fees.maxPriorityFeePerGas?.toString(),
+ });
+ await executeCreateScopeFunctionTxResponse95.transactionResponse?.wait();
+
+ console.log("Zodiac Roles setup is complete!");
+};
+
+main()
+ .then(() => process.exit(0))
+ .catch((error) => {
+ console.error(error);
+ process.exit(1);
+ });
diff --git a/contracts/scripts/gnosis/encodingTX.ts b/contracts/scripts/gnosis/encodingTX.ts
new file mode 100644
index 000000000..67c616c13
--- /dev/null
+++ b/contracts/scripts/gnosis/encodingTX.ts
@@ -0,0 +1,125 @@
+import { ethers } from "hardhat";
+import { OPERATOR_ROLE } from "../../test/utils/constants";
+import { ZkEvmV2Init__factory } from "../../typechain-types";
+
+const main = async () => {
+ const initialL2BlockNumber = "1987654321";
+ const initialStateRootHash = "0x3450000000000000000000000000000000000000000000000000000000000345";
+
+ const proxyAdminContract = "0xd1A02bfB124F5e3970d46111586100E72e7B56bB";
+ const proxyContract = "0xA5d372Cc31C02E945949F8240716c420d8ECBe44";
+ const NewImplementation = "0x1A4635Bd57705D72df06246947A78DAF959D6902";
+ const accountGrantRevokeRole = "0xDdf06fce2C4230A99377E413B0553CDbdf39ef61";
+ const pauseType = "0x0000000000000000000000000000000000000000000000000000000000000000";
+ const rateLimitAmount = "10000000000";
+ const minimumFee = "10000000000";
+
+ const days = 7;
+ const delay = days * 24 * 3600;
+
+ console.log("Encoded TX Output:");
+ console.log("\n");
+
+ const upgradeCallUsingSecurityCouncil = ethers.utils.hexConcat([
+ "0x99a88ec4",
+ ethers.utils.defaultAbiCoder.encode(["address", "address"], [proxyContract, NewImplementation]),
+ ]);
+
+ console.log("Encoded Tx Schedule Upgrade from Security Council :", "\n", upgradeCallUsingSecurityCouncil);
+ console.log("\n");
+
+ const upgradeScheduleCallwithZodiac = ethers.utils.hexConcat([
+ "0x01d5062a",
+ ethers.utils.defaultAbiCoder.encode(
+ ["address", "uint256", "bytes", "bytes32", "bytes32", "uint256"],
+ [
+ proxyAdminContract,
+ 0,
+ upgradeCallUsingSecurityCouncil,
+ ethers.constants.HashZero,
+ ethers.constants.HashZero,
+ delay,
+ ],
+ ),
+ ]);
+
+ console.log("Delay is set to:", delay);
+
+ console.log(
+ "Encoded TX Schedule Upgrade using Zodiac with ",
+ days,
+ "day delay:",
+ "\n",
+ upgradeScheduleCallwithZodiac,
+ );
+ console.log("\n");
+
+ const upgradeCallWithReinitializationUsingSecurityCouncil = ethers.utils.hexConcat([
+ "0x9623609d",
+ ethers.utils.defaultAbiCoder.encode(
+ ["address", "address", "bytes"],
+ [
+ proxyContract,
+ NewImplementation,
+ ZkEvmV2Init__factory.createInterface().encodeFunctionData("initializeV2", [
+ initialL2BlockNumber,
+ initialStateRootHash,
+ ]),
+ ],
+ ),
+ ]);
+
+ console.log(
+ "Encoded Tx Upgrade with Reinitialization from Security Council :",
+ "\n",
+ upgradeCallWithReinitializationUsingSecurityCouncil,
+ );
+ console.log("\n");
+
+ const encodeGrantRole = ethers.utils.hexConcat([
+ "0x2f2ff15d",
+ ethers.utils.defaultAbiCoder.encode(["bytes32", "address"], [OPERATOR_ROLE, accountGrantRevokeRole]),
+ ]);
+
+ console.log("encodeGrantRole:", "\n", encodeGrantRole);
+ console.log("\n");
+
+ const encodeRevokeRole = ethers.utils.hexConcat([
+ "0xd547741f",
+ ethers.utils.defaultAbiCoder.encode(["bytes32", "address"], [OPERATOR_ROLE, accountGrantRevokeRole]),
+ ]);
+
+ console.log("encodeRevokeRole:", "\n", encodeRevokeRole);
+ console.log("\n");
+
+ const encodePauseByType = ethers.utils.hexConcat([
+ "0x8264bd82",
+ ethers.utils.defaultAbiCoder.encode(["bytes32"], [pauseType]),
+ ]);
+
+ console.log("encodePauseByType:", "\n", encodePauseByType);
+ console.log("\n");
+
+ const encodeResetLimitAmount = ethers.utils.hexConcat([
+ "0x557eac73",
+ ethers.utils.defaultAbiCoder.encode(["uint256"], [rateLimitAmount]),
+ ]);
+
+ console.log("encodeResetLimitAmount:", "\n", encodeResetLimitAmount);
+ console.log("\n");
+
+ const encodeSetMinimumFee = ethers.utils.hexConcat([
+ "0x182a7506",
+ ethers.utils.defaultAbiCoder.encode(["uint256"], [minimumFee]),
+ ]);
+
+ console.log("encodeSetMinimumFee:", "\n", encodeSetMinimumFee);
+ console.log("\n");
+};
+
+main()
+ .then(() => process.exit(0))
+ .catch((error) => {
+ console.error(error);
+ process.exit(1);
+ });
diff --git a/contracts/scripts/gnosis/increaseSafeThresholdL1.ts b/contracts/scripts/gnosis/increaseSafeThresholdL1.ts
new file mode 100644
index 000000000..92055b5d2
--- /dev/null
+++ b/contracts/scripts/gnosis/increaseSafeThresholdL1.ts
@@ -0,0 +1,42 @@
+/* eslint-disable @typescript-eslint/no-var-requires */
+import { requireEnv } from "../hardhat/utils";
+import Safe from "@safe-global/safe-core-sdk";
+const { EthersAdapter } = require("@safe-global/protocol-kit");
+const { ethers } = require("ethers");
+
+const main = async () => {
+ const SAFE_ADDRESS = requireEnv("SAFE_ADDRESS");
+ const RPC_URL = requireEnv("BLOCKCHAIN_NODE");
+ const SAFE_OWNER1_PRIVATE_KEY = requireEnv("SAFE_OWNER1_PRIVATE_KEY");
+ const SAFE_NEW_THRESHOLD = requireEnv("SAFE_NEW_THRESHOLD");
+
+ const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
+ const signer = new ethers.Wallet(SAFE_OWNER1_PRIVATE_KEY, provider);
+
+ const ethAdapter = new EthersAdapter({ ethers, signerOrProvider: signer });
+
+ const safeSdk = await Safe.create({ ethAdapter, safeAddress: SAFE_ADDRESS });
+
+ // Display initial threshold & owners
+ const threshold = await safeSdk.getThreshold();
+ console.log("Safe's current threshold: ", threshold);
+
+ const currentOwners = await safeSdk.getOwners();
+ console.log("Safe's owners :", currentOwners);
+
+ // Threshold change
+ const safeTransaction = await safeSdk.createChangeThresholdTx(ethers.BigNumber.from(SAFE_NEW_THRESHOLD));
+ const txResponse = await safeSdk.executeTransaction(safeTransaction);
+ await txResponse.transactionResponse?.wait();
+
+ // Display new threshold
+ const newThreshold = await safeSdk.getThreshold();
+ console.log("Safe's new threshold: ", newThreshold);
+};
+
+main()
+ .then(() => process.exit(0))
+ .catch((error) => {
+ console.error(error);
+ process.exit(1);
+ });
diff --git a/contracts/scripts/gnosis/increaseSafeThresholdL2.ts b/contracts/scripts/gnosis/increaseSafeThresholdL2.ts
new file mode 100644
index 000000000..a00ab2f7a
--- /dev/null
+++ b/contracts/scripts/gnosis/increaseSafeThresholdL2.ts
@@ -0,0 +1,50 @@
+/* eslint-disable @typescript-eslint/no-var-requires */
+import { requireEnv } from "../hardhat/utils";
+import Safe from "@safe-global/safe-core-sdk";
+const { EthersAdapter } = require("@safe-global/protocol-kit");
+const { ethers } = require("ethers");
+import { get1559Fees } from "../utils";
+
+const main = async () => {
+ const SAFE_ADDRESS = requireEnv("SAFE_ADDRESS");
+ const RPC_URL = requireEnv("BLOCKCHAIN_NODE");
+ const SAFE_OWNER1_PRIVATE_KEY = requireEnv("SAFE_OWNER1_PRIVATE_KEY");
+ const SAFE_NEW_THRESHOLD = requireEnv("SAFE_NEW_THRESHOLD");
+
+ const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
+ const signer = new ethers.Wallet(SAFE_OWNER1_PRIVATE_KEY, provider);
+
+ const ethAdapter = new EthersAdapter({ ethers, signerOrProvider: signer });
+
+ const safeSdk = await Safe.create({ ethAdapter, safeAddress: SAFE_ADDRESS });
+
+ const chainId = await ethAdapter.getChainId();
+ console.log(`ChainId: ${chainId}`);
+ const eip1559Fees = await get1559Fees(provider);
+
+ // Display initial threshold & owners
+ const threshold = await safeSdk.getThreshold();
+ console.log("Safe's current threshold: ", threshold);
+
+ const currentOwners = await safeSdk.getOwners();
+ console.log("Safe's owners :", currentOwners);
+
+ // Threshold change
+ const safeTransaction = await safeSdk.createChangeThresholdTx(ethers.BigNumber.from(SAFE_NEW_THRESHOLD));
+ const txResponse = await safeSdk.executeTransaction(safeTransaction, {
+ maxFeePerGas: eip1559Fees.maxFeePerGas?.toString(),
+ maxPriorityFeePerGas: eip1559Fees.maxPriorityFeePerGas?.toString(),
+ });
+ await txResponse.transactionResponse?.wait();
+
+ // Display new threshold
+ const newThreshold = await safeSdk.getThreshold();
+ console.log("Safe's new threshold: ", newThreshold);
+};
+
+main()
+ .then(() => process.exit(0))
+ .catch((error) => {
+ console.error(error);
+ process.exit(1);
+ });
diff --git a/contracts/scripts/hardhat/forceImport.ts b/contracts/scripts/hardhat/forceImport.ts
new file mode 100644
index 000000000..5fb756b12
--- /dev/null
+++ b/contracts/scripts/hardhat/forceImport.ts
@@ -0,0 +1,39 @@
+/*
+Note:
+ Make sure to run this script BEFORE making any changes to the smart contract.
+ The script will generate a hidden directory `.openzeppelin` that will be used by the upgrade script.
+ But this directory must contain data generated from the currently deployed version of the smart contract.
+
+Usage:
+ export CONTRACT_NAME=MyContract
+ export CONTRACT_ADDRESS=0x1234567890123456789012345678901234567890
+
+ npx hardhat --network run scripts/hardhat/forceImport.js
+*/
+
+import { ethers, upgrades } from "hardhat";
+import { requireEnv as env } from "./utils";
+
+const CONTRACT_NAME = env("CONTRACT_NAME");
+const CONTRACT_ADDRESS = env("CONTRACT_ADDRESS");
+
+async function main() {
+ if (!CONTRACT_NAME || !CONTRACT_ADDRESS) {
+ throw new Error(`CONTRACT_ADDRESS and CONTRACT_NAME env variables are undefined.`);
+ }
+ console.log(`Importing contract at ${CONTRACT_ADDRESS}`);
+
+ const contract = await ethers.getContractFactory(CONTRACT_NAME);
+
+ console.log("Importing contract");
+ await upgrades.forceImport(CONTRACT_ADDRESS, contract, {
+ kind: "transparent",
+ });
+}
+
+main()
+ .then(() => process.exit(0))
+ .catch((error) => {
+ console.error(error);
+ process.exit(1);
+ });
diff --git a/contracts/scripts/hardhat/postCompile.ts b/contracts/scripts/hardhat/postCompile.ts
new file mode 100644
index 000000000..cf4d20f79
--- /dev/null
+++ b/contracts/scripts/hardhat/postCompile.ts
@@ -0,0 +1,87 @@
+import "colors";
+import fs from "fs";
+import path from "path";
+import { artifacts } from "hardhat";
+import Diff from "diff";
+
+const UNCHANGED_COLOR = "grey";
+const MAX_UNCHANGED_PART_LEN = 100;
+
+const EXPOSED_CONTRACTS = ["L2MessageService", "ZkEvmV2", "TimeLock"];
+
+async function main() {
+ const checkOnly = process.env.CHECK_ONLY === "1";
+
+ for (const contract of EXPOSED_CONTRACTS) {
+ const abiPath = path.resolve("abi", contract + ".abi");
+
+ if (checkOnly) {
+ const currentAbi = JSON.parse(fs.readFileSync(abiPath, "utf8"));
+ const stringifiedCurrentAbi = JSON.stringify(currentAbi, null, 2);
+
+ const { stringifiedAbi, abi } = await readAbi(contract);
+
+ if (stringifiedAbi !== stringifiedCurrentAbi) {
+ showDiff(abi, currentAbi);
+
+ throw new Error(`${contract} ABI has changed, please update it and commit the changes to the repository`);
+ }
+ } else {
+ const { stringifiedAbi } = await readAbi(contract);
+
+ fs.writeFileSync(abiPath, stringifiedAbi);
+ }
+ }
+}
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+function showDiff(abi: any[], currentAbi: any[]) {
+ const diff = Diff.diffJson(abi, currentAbi);
+
+ diff.forEach((part) => {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ let color: any;
+
+ if (part.added) {
+ color = "green";
+ } else if (part.removed) {
+ color = "red";
+ } else {
+ color = UNCHANGED_COLOR;
+ }
+
+ let text = part.value;
+ if (color === UNCHANGED_COLOR) {
+ text = hideDiffChange(text);
+ }
+
+ console.error(text[color]);
+ });
+}
+
+async function readAbi(contract: string) {
+ const artifact = await artifacts.readArtifact(contract);
+ const abi = artifact.abi;
+
+ const stringifiedAbi = JSON.stringify(abi, null, 2);
+
+ return { stringifiedAbi, abi };
+}
+
+function hideDiffChange(diffPart: string) {
+ if (diffPart.length <= MAX_UNCHANGED_PART_LEN) {
+ return diffPart;
+ }
+
+ const firstPart = diffPart.slice(0, MAX_UNCHANGED_PART_LEN / 2);
+ const endPart = diffPart.slice(diffPart.length - MAX_UNCHANGED_PART_LEN / 2);
+
+ return `${firstPart}...${endPart}`;
+}
+
+main()
+ .then(() => process.exit(0))
+ .catch((error) => {
+ console.error(error);
+ process.exit(1);
+ });
diff --git a/contracts/scripts/hardhat/printNonces.ts b/contracts/scripts/hardhat/printNonces.ts
new file mode 100644
index 000000000..406fad56a
--- /dev/null
+++ b/contracts/scripts/hardhat/printNonces.ts
@@ -0,0 +1,32 @@
+import fs from "fs";
+import { ethers } from "hardhat";
+
+const monitoredAccount = process.env.MONITORED_ACCOUNT || "/node-data/test/keys/operator_1.acc";
+
+async function main() {
+ console.log(getCurrentDate() + "Starting printing nonces script");
+ const credentials = JSON.parse(fs.readFileSync(monitoredAccount, "utf8"));
+ const privateKey = "0x" + credentials.account_key.priv_key;
+ const wallet = new ethers.Wallet(privateKey, ethers.provider);
+ console.log(getCurrentDate() + `Going to print nonces for address: ${wallet.address}`);
+ const result = await ethers.provider.send("eth_chainId", []);
+ console.log(getCurrentDate() + "eth_chainId", { result });
+
+ async function listenLoop() {
+ const count = await ethers.provider.getTransactionCount(wallet.address);
+ const currentDate = getCurrentDate();
+ console.log(`${currentDate} - transaction count for ${wallet.address}: ${count} `);
+ setTimeout(listenLoop, 1000);
+ }
+
+ await listenLoop();
+}
+
+function getCurrentDate() {
+ return "[" + new Date().toUTCString() + "] ";
+}
+
+main().catch((error) => {
+ console.error(error);
+ process.exit(1);
+});
diff --git a/contracts/scripts/hardhat/upgrade.ts b/contracts/scripts/hardhat/upgrade.ts
new file mode 100644
index 000000000..636c8b222
--- /dev/null
+++ b/contracts/scripts/hardhat/upgrade.ts
@@ -0,0 +1,58 @@
+/*
+Note:
+ Deploying an upgradeable contract should generate a hidden directory `.openzeppelin`.
+ But because we're deploying from a docker container (or in a cluster) this directory won't be here.
+
+ So you'll first need to run the forceImport script that's in the same directory.
+
+Usage:
+ export CONTRACT_NAME=MyContract
+ export CONTRACT_ADDRESS=0x1234567890123456789012345678901234567890
+
+ # if you need to specify a different private key then the one in the files
+ export PRIVATE_KEY=
+
+ npx hardhat --network run scripts/hardhat/upgrade.js
+*/
+
+import { ethers, upgrades } from "hardhat";
+import { requireEnv as env } from "./utils";
+
+const CONTRACT_NAME = env("CONTRACT_NAME");
+const CONTRACT_ADDRESS = env("CONTRACT_ADDRESS");
+
+async function main() {
+ if (!CONTRACT_NAME || !CONTRACT_ADDRESS) {
+ throw new Error(`CONTRACT_ADDRESS and CONTRACT_NAME env variables are undefined.`);
+ }
+ console.log(`Upgrading contract at ${CONTRACT_ADDRESS}`);
+
+ const contract = await ethers.getContractFactory(CONTRACT_NAME);
+
+ console.log("Upgrading...");
+ try {
+ await upgrades.validateUpgrade(CONTRACT_ADDRESS, contract, {
+ kind: "transparent",
+ });
+
+ await upgrades.upgradeProxy(CONTRACT_ADDRESS, contract, {
+ kind: "transparent",
+ });
+
+ console.log(`Upgraded contract at ${CONTRACT_ADDRESS} with a new version of ${CONTRACT_NAME}`);
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ } catch (error: any) {
+ console.error(
+ "Failed to upgrade the proxy contract, check the error below. You might have to run the forceImport script first on a pre-upgrade version of the smart contract source to regenerate .openzeppelin files\n",
+ error.message,
+ );
+ throw error;
+ }
+}
+
+main()
+ .then(() => process.exit(0))
+ .catch((error) => {
+ console.error(error);
+ process.exit(1);
+ });
diff --git a/contracts/scripts/hardhat/utils.ts b/contracts/scripts/hardhat/utils.ts
new file mode 100644
index 000000000..a596945ec
--- /dev/null
+++ b/contracts/scripts/hardhat/utils.ts
@@ -0,0 +1,75 @@
+import { ethers, upgrades } from "hardhat";
+import { providers } from "ethers";
+import { DeployProxyOptions } from "@openzeppelin/hardhat-upgrades/dist/utils";
+import { FactoryOptions } from "hardhat/types";
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+async function deployFromFactory(
+ contractName: string,
+ provider: providers.JsonRpcProvider | null = null,
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ ...args: any[]
+) {
+ const skipLog = process.env.SKIP_DEPLOY_LOG === "true" || false;
+ if (!skipLog) {
+ console.log(`Going to deploy ${contractName}`);
+ }
+
+ const factory = await ethers.getContractFactory(contractName);
+ if (provider) {
+ factory.connect(provider.getSigner());
+ }
+ const contract = await factory.deploy(...args);
+ if (!skipLog) {
+ console.log(`${contractName} deployment transaction has been sent, waiting...`, {
+ nonce: contract.deployTransaction.nonce,
+ hash: contract.deployTransaction.hash,
+ gasPrice: contract.deployTransaction.gasPrice?.toString(),
+ maxFeePerGas: contract.deployTransaction.maxFeePerGas?.toString(),
+ maxPriorityFeePerGas: contract.deployTransaction.maxPriorityFeePerGas?.toString(),
+ gasLimit: contract.deployTransaction.gasLimit.toString(),
+ });
+ }
+ const afterDeploy = await contract.deployed();
+ if (!skipLog) {
+ console.log(`${contractName} artifact has been deployed in tx-hash=${afterDeploy.deployTransaction.hash}`);
+ }
+ return contract;
+}
+
+async function deployUpgradableFromFactory(
+ contractName: string,
+ args?: unknown[],
+ opts?: DeployProxyOptions,
+ factoryOpts?: FactoryOptions,
+) {
+ const skipLog = process.env.SKIP_DEPLOY_LOG === "true" || false;
+ if (!skipLog) {
+ console.log(`Going to deploy upgradable ${contractName}`);
+ }
+ const factory = await ethers.getContractFactory(contractName, factoryOpts);
+ const contract = await upgrades.deployProxy(factory, args, opts);
+ if (!skipLog) {
+ console.log(`Upgradable ${contractName} deployment transaction has been sent, waiting...`, {
+ hash: contract.deployTransaction.hash,
+ gasPrice: contract.deployTransaction.gasPrice?.toString(),
+ gasLimit: contract.deployTransaction.gasLimit.toString(),
+ });
+ }
+ const afterDeploy = await contract.deployed();
+ if (!skipLog) {
+ console.log(`${contractName} artifact has been deployed in tx-hash=${afterDeploy.deployTransaction.hash}`);
+ }
+ return contract;
+}
+
+function requireEnv(name: string): string {
+ const envVariable = process.env[name];
+ if (!envVariable) {
+ throw new Error(`Missing ${name} environment variable`);
+ }
+
+ return envVariable;
+}
+
+export { deployFromFactory, deployUpgradableFromFactory, requireEnv };
diff --git a/contracts/scripts/operational/getCurrentFinalizedBlockNumber.ts b/contracts/scripts/operational/getCurrentFinalizedBlockNumber.ts
new file mode 100644
index 000000000..9e00441ff
--- /dev/null
+++ b/contracts/scripts/operational/getCurrentFinalizedBlockNumber.ts
@@ -0,0 +1,34 @@
+import { ethers } from "hardhat";
+import { requireEnv } from "../hardhat/utils";
+
+/*
+ *******************************************************************************************
+ 1. Set the CONTRACT_TYPE of the proxy - e.g. ZkEvmV2
+ 2. Set the PROXY_ADDRESS for the contract
+ *******************************************************************************************
+
+ *******************************************************************************************
+ npx hardhat run --network zkevm_dev scripts/operational/getCurrentFinalizedBlockNumber.ts
+ *******************************************************************************************
+*/
+
+async function main() {
+ const contractType = requireEnv("CONTRACT_TYPE");
+ const proxyAddress = requireEnv("PROXY_ADDRESS");
+
+ if (!contractType || !proxyAddress) {
+ throw new Error(`PROXY_ADDRESS and CONTRACT_NAME env variables are undefined.`);
+ }
+
+ const zkEvmContract = await ethers.getContractAt(contractType, proxyAddress);
+ const blockNum = await zkEvmContract.currentL2BlockNumber();
+
+ console.log(blockNum);
+}
+
+main()
+ .then(() => process.exit(0))
+ .catch((error) => {
+ console.error(error);
+ process.exit(1);
+ });
diff --git a/contracts/scripts/operational/grantContractRoles.ts b/contracts/scripts/operational/grantContractRoles.ts
new file mode 100644
index 000000000..bfef02e23
--- /dev/null
+++ b/contracts/scripts/operational/grantContractRoles.ts
@@ -0,0 +1,41 @@
+import { requireEnv } from "../hardhat/utils";
+import { ethers } from "hardhat";
+
+/*
+ *******************************************************************************************
+ 1. Set the ADMIN_ADDRESS to the Safe address
+ 2. Set the PROXY_ADDRESS for the contract
+ 3. Set the CONTRACT_TYPE of the proxy - e.g. ZkEvmV2
+ 4. Set the CONTRACT_ROLES comma separated, e.g "0x356a809dfdea9198dd76fb76bf6d403ecf13ea675eb89e1eda2db2c4a4676a26,0x1185e52d62bfbbea270e57d3d09733d221b53ab7a18bae82bb3c6c74bab16d82,0x0000000000000000000000000000000000000000000000000000000000000000"
+ *******************************************************************************************
+ NB: Be sure to have use the roles initially set to the security council EOA before changing
+ *******************************************************************************************
+ npx hardhat run --network zkevm_dev scripts/operational/grantContractRoles.ts
+ *******************************************************************************************
+*/
+
+async function main() {
+ const adminAddress = requireEnv("ADMIN_ADDRESS");
+ const proxyAddress = requireEnv("PROXY_ADDRESS");
+ const contractType = requireEnv("CONTRACT_TYPE");
+ const contractRoles = requireEnv("CONTRACT_ROLES");
+
+ const contract = await ethers.getContractAt(contractType, proxyAddress);
+
+ const rolesArray = contractRoles?.split(",");
+ for (let i = 0; i < rolesArray.length; i++) {
+ console.log(`Granting ${rolesArray[i]} to ${adminAddress}`);
+ const tx = await contract.grantRole(rolesArray[i], adminAddress);
+ console.log("Waiting for transaction to process");
+ await tx.wait();
+ }
+
+ console.log("Done");
+}
+
+main()
+ .then(() => process.exit(0))
+ .catch((error) => {
+ console.error(error);
+ process.exit(1);
+ });
diff --git a/contracts/scripts/operational/renounceContractRoles.ts b/contracts/scripts/operational/renounceContractRoles.ts
new file mode 100644
index 000000000..b00cf667a
--- /dev/null
+++ b/contracts/scripts/operational/renounceContractRoles.ts
@@ -0,0 +1,56 @@
+import { requireEnv } from "../hardhat/utils";
+import { ethers } from "hardhat";
+
+/*
+ *******************************************************************************************
+ 1. Set the OLD_ADMIN_ADDRESS to the EOA address
+ 2. Set the NEW_ADMIN_ADDRESS that you have just granted the roles to
+ 3. Set the PROXY_ADDRESS for the contract
+ 4. Set the CONTRACT_TYPE of the proxy - e.g. ZkEvmV2
+ 5. Set the CONTRACT_ROLES comma separated, e.g "0x356a809dfdea9198dd76fb76bf6d403ecf13ea675eb89e1eda2db2c4a4676a26,0x1185e52d62bfbbea270e57d3d09733d221b53ab7a18bae82bb3c6c74bab16d82,0x0000000000000000000000000000000000000000000000000000000000000000"
+ *******************************************************************************************
+ NB: Be sure to have use the roles initially set to the security council EOA before changing
+
+ DO NOT CALL THIS UNTIL YOU HAVE GIVEN THE NEW ADDRESS ALLL THE ROLES YOU ARE REVOKING
+
+ MAKE SURE THAT THE DEFAULT ADMIN ROLE IS LAST AS IT IS REVOKING/RENOUNCING FROM SELF
+ *******************************************************************************************
+ npx hardhat run --network zkevm_dev scripts/operational/renounceContractRoles.ts
+ *******************************************************************************************
+*/
+
+async function main() {
+ const oldAdminAddress = requireEnv("OLD_ADMIN_ADDRESS");
+ const newAdminAddress = requireEnv("NEW_ADMIN_ADDRESS");
+ const proxyAddress = requireEnv("PROXY_ADDRESS");
+ const contractType = requireEnv("CONTRACT_TYPE");
+ const contractRoles = requireEnv("CONTRACT_ROLES");
+
+ const contract = await ethers.getContractAt(contractType, proxyAddress);
+
+ const rolesArray = contractRoles?.split(",");
+ for (let i = 0; i < rolesArray.length; i++) {
+ console.log(
+ `Checking the new admin ${newAdminAddress} has ${rolesArray[i]} before revoking the old admin ${oldAdminAddress}`,
+ );
+ const newAdminHasRole = await contract.hasRole(rolesArray[i], newAdminAddress);
+ if (newAdminHasRole) {
+ console.log(`Revoking ${rolesArray[i]} from ${oldAdminAddress}`);
+ const tx = await contract.renounceRole(rolesArray[i], oldAdminAddress);
+
+ console.log("Waiting for transaction to process");
+ await tx.wait();
+ } else {
+ console.log(`New admin does not have ${rolesArray[i]}, skipping`);
+ }
+ }
+
+ console.log("Done");
+}
+
+main()
+ .then(() => process.exit(0))
+ .catch((error) => {
+ console.error(error);
+ process.exit(1);
+ });
diff --git a/contracts/scripts/operational/setRateLimit.ts b/contracts/scripts/operational/setRateLimit.ts
new file mode 100644
index 000000000..aea955780
--- /dev/null
+++ b/contracts/scripts/operational/setRateLimit.ts
@@ -0,0 +1,46 @@
+import { requireEnv } from "../hardhat/utils";
+import { ethers } from "hardhat";
+
+/*
+ *******************************************************************************************
+ 1. Set the MESSAGE_SERVICE_ADDRESS
+ 2. Set the MESSAGE_SERVICE_TYPE ( e.g. L2MessageService )
+ 3. Set the WITHDRAW_LIMIT_IN_WEI value
+ *******************************************************************************************
+
+ *******************************************************************************************
+ npx hardhat run --network zkevm_dev scripts/operational/setRateLimit.ts
+ *******************************************************************************************
+*/
+
+async function main() {
+ const messageServiceAddress = requireEnv("MESSAGE_SERVICE_ADDRESS");
+ const messageServiceContractType = requireEnv("MESSAGE_SERVICE_TYPE");
+ const withdrawLimitInWei = requireEnv("WITHDRAW_LIMIT_IN_WEI");
+
+ const messageService = await ethers.getContractAt(messageServiceContractType, messageServiceAddress);
+
+ // get existing limit
+ const limitInWei = await messageService.limitInWei();
+ console.log(
+ `Starting with rate limit in wei of ${limitInWei} at ${messageServiceAddress} of type ${messageServiceContractType}`,
+ );
+
+ // set limit
+
+ const updateTx = await messageService.resetRateLimitAmount(withdrawLimitInWei);
+ await updateTx.wait();
+
+ console.log(`Changed rate limit in wei to ${withdrawLimitInWei} at ${messageServiceAddress}`);
+
+ // get new updated limited
+ const newLimitInWei = await messageService.limitInWei();
+ console.log(`Validated rate limit in wei of ${newLimitInWei} at ${messageServiceAddress}`);
+}
+
+main()
+ .then(() => process.exit(0))
+ .catch((error) => {
+ console.error(error);
+ process.exit(1);
+ });
diff --git a/contracts/scripts/operational/setVerifierAddress.ts b/contracts/scripts/operational/setVerifierAddress.ts
new file mode 100644
index 000000000..54a0e9b5c
--- /dev/null
+++ b/contracts/scripts/operational/setVerifierAddress.ts
@@ -0,0 +1,38 @@
+import { requireEnv } from "../hardhat/utils";
+import { ethers } from "hardhat";
+import { BigNumber } from "ethers";
+
+/*
+ *******************************************************************************************
+ 1. Deploy the verifier and get the address
+ 2. Run this script matching the correct PROOF_TYPE
+ *******************************************************************************************
+
+ *******************************************************************************************
+ npx hardhat run --network zkevm_dev scripts/operational/setVerifierAddress.ts
+ *******************************************************************************************
+*/
+
+async function main() {
+ const proofType = requireEnv("VERIFIER_PROOF"); // todo rename this once checking it doesn't break anything else
+ const zkEvmAddress = requireEnv("ZKEVMV2_ADDRESS");
+ const verifierAddress = requireEnv("VERIFIER_ADDRESS");
+
+ const zkEvmV2 = await ethers.getContractAt("ZkEvmV2", zkEvmAddress);
+
+ console.log(`Setting verifier address ${verifierAddress} of type ${proofType}`);
+ const tx = await zkEvmV2.setVerifierAddress(verifierAddress, BigNumber.from(proofType));
+
+ console.log("Waiting for transaction to process");
+ await tx.wait();
+
+ const checkVerifierIsSet = await zkEvmV2.verifiers(BigNumber.from(proofType));
+ console.log(`ZkEvmV2 implementation added ${checkVerifierIsSet} as new verifier`);
+}
+
+main()
+ .then(() => process.exit(0))
+ .catch((error) => {
+ console.error(error);
+ process.exit(1);
+ });
diff --git a/contracts/scripts/operational/transferProxyAdminOwnership.ts b/contracts/scripts/operational/transferProxyAdminOwnership.ts
new file mode 100644
index 000000000..92524c64f
--- /dev/null
+++ b/contracts/scripts/operational/transferProxyAdminOwnership.ts
@@ -0,0 +1,40 @@
+import { requireEnv } from "../hardhat/utils";
+import { ethers, upgrades } from "hardhat";
+
+/*
+ *******************************************************************************************
+ If upgrading a to a timelock controller
+ be sure to deploy the timelock controller and
+ use the timelock controller address for the PROXY_ADMIN_OWNER_ADDRESS
+ *******************************************************************************************
+
+ NB: Be sure of who owns the Timelock before transferring admin to the
+ timelock controller. There is the potential to brick ownership
+
+ *******************************************************************************************
+ npx hardhat run --network zkevm_dev scripts/operational/transferProxyAdminOwnership.ts
+ *******************************************************************************************
+*/
+
+async function main() {
+ const proxyAdminOwnerAddress = requireEnv("PROXY_ADMIN_OWNER_ADDRESS");
+ const proxyAddress = requireEnv("PROXY_ADDRESS");
+ const contractType = requireEnv("CONTRACT_TYPE");
+
+ const proxyContract = await ethers.getContractFactory(contractType);
+ await upgrades.forceImport(proxyAddress, proxyContract, {
+ kind: "transparent",
+ });
+
+ // // CHANGE OWNERSHIP OF PROXY ADMIN
+ console.log(`Changing proxy admin of ${contractType} at ${proxyAddress} to owner: ${proxyAdminOwnerAddress}`);
+ await upgrades.admin.transferProxyAdminOwnership(proxyAdminOwnerAddress);
+ console.log("Done");
+}
+
+main()
+ .then(() => process.exit(0))
+ .catch((error) => {
+ console.error(error);
+ process.exit(1);
+ });
diff --git a/contracts/scripts/tokenBridge/deploy-1.ts b/contracts/scripts/tokenBridge/deploy-1.ts
new file mode 100644
index 000000000..9d138bf9f
--- /dev/null
+++ b/contracts/scripts/tokenBridge/deploy-1.ts
@@ -0,0 +1,102 @@
+import { ethers, upgrades, network, run } from "hardhat";
+import { delay, storeAddress } from "../../utils/storeAddress";
+import { requireEnv } from "../hardhat/utils";
+import { SupportedChainIds } from "./supportedNetworks";
+
+export async function main() {
+ const messageServiceAddress = requireEnv("MESSAGE_SERVICE_ADDRESS");
+ const zkEvmV2Address = requireEnv("ZKEVMV2_ADDRESS");
+ const [owner] = await ethers.getSigners();
+ const chainId = await owner.getChainId();
+
+ if (!(chainId in SupportedChainIds)) {
+ throw `Chaind Id ${chainId} not supported`;
+ }
+
+ let messageServiceAddr;
+ let reservedAddresses;
+ let chainIds = [SupportedChainIds.MAINNET, SupportedChainIds.LINEA];
+ switch (chainId) {
+ case SupportedChainIds.LINEA:
+ case SupportedChainIds.LINEA_TESTNET:
+ messageServiceAddr = messageServiceAddress;
+ reservedAddresses = process.env.L2_RESERVED_TOKEN_ADDRESSES
+ ? process.env.L2_RESERVED_TOKEN_ADDRESSES.split(" ")
+ : [];
+ if (chainId === SupportedChainIds.LINEA) {
+ chainIds = [SupportedChainIds.LINEA, SupportedChainIds.MAINNET];
+ } else {
+ chainIds = [SupportedChainIds.LINEA_TESTNET, SupportedChainIds.GOERLI];
+ }
+ break;
+ case SupportedChainIds.GOERLI:
+ case SupportedChainIds.MAINNET:
+ messageServiceAddr = zkEvmV2Address;
+ reservedAddresses = process.env.L1_RESERVED_TOKEN_ADDRESSES
+ ? process.env.L1_RESERVED_TOKEN_ADDRESSES.split(" ")
+ : [];
+ if (chainId === SupportedChainIds.GOERLI) {
+ chainIds = [SupportedChainIds.GOERLI, SupportedChainIds.LINEA_TESTNET];
+ }
+ break;
+ }
+
+ // Deploy beacon for bridged token
+ const BridgedToken = await ethers.getContractFactory("BridgedToken");
+
+ const bridgedToken = await upgrades.deployBeacon(BridgedToken);
+ await bridgedToken.deployed();
+ storeAddress("BridgedToken", bridgedToken.address, network.name);
+
+ console.log(`BridgedToken beacon deployed on ${network.name}, at address:`, bridgedToken.address);
+
+ // Deploying TokenBridge
+ const TokenBridgeFactory = await ethers.getContractFactory("TokenBridge");
+
+ const tokenBridge = await upgrades.deployProxy(TokenBridgeFactory, [
+ owner.address,
+ messageServiceAddr,
+ bridgedToken.address,
+ chainIds[0],
+ chainIds[1],
+ reservedAddresses,
+ ]);
+ await tokenBridge.deployed();
+ storeAddress("TokenBridge", tokenBridge.address, network.name);
+
+ console.log(`TokenBridge deployed on ${network.name}, at address: ${tokenBridge.address}`);
+
+ // Verify contracts on etherscan, we wait some time so that the contracts can be
+ // propagated to the etherscan backend
+ // We have to run the verify on the implementation contracts
+ await delay(30000);
+ console.log("Etherscan verification ongoing...");
+ // Verify TokenBridge
+ try {
+ const bridgedTokenImplAddr = await upgrades.beacon.getImplementationAddress(bridgedToken.address);
+ await run("verify", {
+ address: bridgedTokenImplAddr,
+ });
+ const tokenBridgeImplAddr = await upgrades.erc1967.getImplementationAddress(tokenBridge.address);
+ await run("verify", {
+ address: tokenBridgeImplAddr,
+ });
+ } catch (err) {
+ console.log(`Error happened during verification: ${err}`);
+ }
+
+ console.log("Etherscan verification done.");
+}
+
+// We recommend this pattern to be able to use async/await everywhere
+// and properly handle errors.
+main()
+ .then(() => {
+ process.exitCode = 0;
+ process.exit();
+ })
+ .catch((error) => {
+ console.error(error);
+ process.exitCode = 1;
+ process.exit();
+ });
diff --git a/contracts/scripts/tokenBridge/deploy-2.ts b/contracts/scripts/tokenBridge/deploy-2.ts
new file mode 100644
index 000000000..73c5016f1
--- /dev/null
+++ b/contracts/scripts/tokenBridge/deploy-2.ts
@@ -0,0 +1,65 @@
+import { ethers, network, upgrades } from "hardhat";
+import { default as deployments } from "../../deployments.json";
+import { SupportedChainIds } from "./supportedNetworks";
+
+export async function main() {
+ const [owner] = await ethers.getSigners();
+ const chainId = await owner.getChainId();
+
+ if (!(chainId in SupportedChainIds)) {
+ throw `Chaind Id ${chainId} not supported`;
+ }
+
+ if (!deployments.zkevm_dev.TokenBridge || !deployments.l2.TokenBridge) {
+ throw "The TokenBridge needs to be deployed on both layers first";
+ }
+
+ let tokenBridgeAddress;
+ let remoteTokenBridgeAddress;
+ let lineaSafeAddr;
+ switch (chainId) {
+ case SupportedChainIds.MAINNET:
+ case SupportedChainIds.GOERLI:
+ tokenBridgeAddress = deployments.zkevm_dev.TokenBridge;
+ remoteTokenBridgeAddress = deployments.l2.TokenBridge;
+ lineaSafeAddr = process.env.LINEA_SAFE_L1 ? process.env.LINEA_SAFE_L1 : "";
+ break;
+ case SupportedChainIds.LINEA:
+ case SupportedChainIds.LINEA_TESTNET:
+ tokenBridgeAddress = deployments.l2.TokenBridge;
+ remoteTokenBridgeAddress = deployments.zkevm_dev.TokenBridge;
+ lineaSafeAddr = process.env.LINEA_SAFE_L2 ? process.env.LINEA_SAFE_L2 : "";
+ break;
+ }
+
+ if (!lineaSafeAddr) {
+ throw `Linea Safe address is not initialized`;
+ }
+
+ const TokenBridge = await ethers.getContractFactory("TokenBridge");
+ const tokenBridge = await TokenBridge.attach(tokenBridgeAddress);
+ let tx = await tokenBridge.setRemoteTokenBridge(remoteTokenBridgeAddress);
+
+ await tx.wait();
+
+ console.log(`RemoteTokenBridge set for the TokenBridge on: ${network.name}`);
+
+ tx = await tokenBridge.transferOwnership(lineaSafeAddr);
+ await tx.wait();
+ await upgrades.admin.transferProxyAdminOwnership(lineaSafeAddr);
+
+ console.log(`TokenBridge ownership and proxy admin set to: ${lineaSafeAddr}`);
+}
+
+// We recommend this pattern to be able to use async/await everywhere
+// and properly handle errors.
+main()
+ .then(() => {
+ process.exitCode = 0;
+ process.exit();
+ })
+ .catch((error) => {
+ console.error(error);
+ process.exitCode = 1;
+ process.exit();
+ });
diff --git a/contracts/scripts/tokenBridge/gasEstimation/gasEstimation.ts b/contracts/scripts/tokenBridge/gasEstimation/gasEstimation.ts
new file mode 100644
index 000000000..5e0d5dedf
--- /dev/null
+++ b/contracts/scripts/tokenBridge/gasEstimation/gasEstimation.ts
@@ -0,0 +1,99 @@
+import { ethers, upgrades } from "hardhat";
+import { deployBridgedTokenBeacon } from "../test/deployBridgedTokenBeacon";
+import { deployTokens } from "../test/deployTokens";
+import { BigNumber } from "ethers";
+import { getPermitData } from "../../../test/tokenBridge/utils/permitHelper";
+
+const initialUserBalance = BigNumber.from(10 ** 9);
+const bridgeAmount = 70;
+const DEPLOYED_STATUS = ethers.utils.getAddress("0x0000000000000000000000000000000000000333");
+const deadline = ethers.constants.MaxUint256;
+
+/**
+ * Simple script to test the gas cost of the method bridgeToken
+ */
+async function main() {
+ const [user] = await ethers.getSigners();
+ const { chainId } = await ethers.provider.getNetwork();
+
+ // Deploy beacon for bridged tokens
+ const tokenBeacons = await deployBridgedTokenBeacon();
+
+ // Deploy the messageService
+ const MockMessageServiceV2 = await ethers.getContractFactory("MockMessageServiceV2");
+ const mockMessageServiceV2 = await MockMessageServiceV2.deploy();
+ await mockMessageServiceV2.deployed();
+
+ // Deploy tokenBridges
+ const TokenBridgeFactory = await ethers.getContractFactory("MockTokenBridge");
+ const l1TokenBridge = await upgrades.deployProxy(TokenBridgeFactory, [
+ user.address,
+ mockMessageServiceV2.address,
+ tokenBeacons.l1TokenBeacon.address,
+ [],
+ ]);
+ await l1TokenBridge.deployed();
+
+ const l2TokenBridge = await upgrades.deployProxy(TokenBridgeFactory, [
+ user.address,
+ mockMessageServiceV2.address,
+ tokenBeacons.l2TokenBeacon.address,
+ [],
+ ]);
+ await l2TokenBridge.deployed();
+
+ // Setting reciprocal addresses of TokenBridges
+ await l1TokenBridge.setRemoteTokenBridge(l2TokenBridge.address);
+ await l2TokenBridge.setRemoteTokenBridge(l1TokenBridge.address);
+
+ // Deploy test tokens
+ const tokens = await deployTokens(false);
+
+ // Mint tokens for user and approve bridge
+ for (const name in tokens) {
+ const token = tokens[name];
+ await token.mint(user.address, initialUserBalance);
+ await token.connect(user).approve(l1TokenBridge.address, ethers.constants.MaxUint256);
+ }
+
+ // Create a bridgedToken to test to bridge with the metadata
+ const BridgedToken = await ethers.getContractFactory("BridgedToken");
+ const abcToken = await upgrades.deployBeaconProxy(tokenBeacons.l1TokenBeacon.address, BridgedToken, [
+ "AbcTokendfgrdgredt",
+ "ABC",
+ 18,
+ ]);
+
+ // Estimate gas cost without permitData
+ let gasCost = await l1TokenBridge.connect(user).estimateGas.bridgeToken(tokens.L1DAI.address, 10, user.address);
+ console.log("basic bridgeToken: ", gasCost.toString());
+
+ // Prepare data for permit calldata
+ await abcToken.mint(user.address, initialUserBalance);
+ const nonce = await abcToken.nonces(user.address);
+ const permitData = await getPermitData(user, abcToken, nonce, chainId, l1TokenBridge.address, bridgeAmount, deadline);
+
+ gasCost = await l1TokenBridge
+ .connect(user)
+ .estimateGas.bridgeTokenWithPermit(abcToken.address, bridgeAmount, user.address, permitData);
+ console.log("bridgeToken with permit: ", gasCost.toString());
+
+ await l1TokenBridge.setNativeMappingValue(tokens.L1DAI.address, DEPLOYED_STATUS);
+
+ // console.log(await l1TokenBridge.nativeToBridgedToken(tokens.L1DAI.address));
+
+ gasCost = await l1TokenBridge.connect(user).estimateGas.bridgeToken(tokens.L1DAI.address, 10, user.address);
+ console.log("bridgeToken after confirmDeploy: ", gasCost.toString());
+
+ await l1TokenBridge.setNativeMappingValue(abcToken.address, DEPLOYED_STATUS);
+
+ gasCost = await l1TokenBridge
+ .connect(user)
+ .estimateGas.bridgeTokenWithPermit(abcToken.address, bridgeAmount, user.address, permitData);
+ console.log("bridgeToken with permit after confirmDeploy: ", gasCost.toString());
+}
+
+main().catch((error) => {
+ console.error(error);
+ process.exitCode = 1;
+});
diff --git a/contracts/scripts/tokenBridge/supportedNetworks.ts b/contracts/scripts/tokenBridge/supportedNetworks.ts
new file mode 100644
index 000000000..4357ffe25
--- /dev/null
+++ b/contracts/scripts/tokenBridge/supportedNetworks.ts
@@ -0,0 +1,6 @@
+export enum SupportedChainIds {
+ MAINNET = 1,
+ GOERLI = 5,
+ LINEA_TESTNET = 59140,
+ LINEA = 59144,
+}
diff --git a/contracts/scripts/tokenBridge/test/deployBridgedTokenBeacon.ts b/contracts/scripts/tokenBridge/test/deployBridgedTokenBeacon.ts
new file mode 100644
index 000000000..e93f23a3e
--- /dev/null
+++ b/contracts/scripts/tokenBridge/test/deployBridgedTokenBeacon.ts
@@ -0,0 +1,26 @@
+import { ethers, upgrades, network } from "hardhat";
+import { storeAddress } from "../../../utils/storeAddress";
+
+export async function deployBridgedTokenBeacon(verbose = false) {
+ const BridgedToken = await ethers.getContractFactory("BridgedToken");
+
+ const l1TokenBeacon = await upgrades.deployBeacon(BridgedToken);
+ await l1TokenBeacon.deployed();
+ if (verbose) {
+ console.log("L1TokenBeacon deployed, at address:", l1TokenBeacon.address);
+ }
+
+ const l2TokenBeacon = await upgrades.deployBeacon(BridgedToken);
+ await l2TokenBeacon.deployed();
+ if (verbose) {
+ console.log("L2TokenBeacon deployed, at address:", l2TokenBeacon.address);
+ }
+
+ // @TODO:
+ // - Verify contracts on Etherscan
+
+ storeAddress("l1TokenBeacon", l1TokenBeacon.address, network.name);
+ storeAddress("l2TokenBeacon", l2TokenBeacon.address, network.name);
+
+ return { l1TokenBeacon, l2TokenBeacon };
+}
diff --git a/contracts/scripts/tokenBridge/test/deployMock.ts b/contracts/scripts/tokenBridge/test/deployMock.ts
new file mode 100644
index 000000000..ad843a47a
--- /dev/null
+++ b/contracts/scripts/tokenBridge/test/deployMock.ts
@@ -0,0 +1,11 @@
+import { deployTokenBridgeWithMockMessaging } from "./deployTokenBridges";
+
+deployTokenBridgeWithMockMessaging(true)
+ .then(() => {
+ process.exitCode = 0;
+ process.exit();
+ })
+ .catch((error) => {
+ console.error(error);
+ process.exitCode = 1;
+ });
diff --git a/contracts/scripts/tokenBridge/test/deployTokenBridges.ts b/contracts/scripts/tokenBridge/test/deployTokenBridges.ts
new file mode 100644
index 000000000..518a0e246
--- /dev/null
+++ b/contracts/scripts/tokenBridge/test/deployTokenBridges.ts
@@ -0,0 +1,65 @@
+import { ethers, upgrades } from "hardhat";
+
+import { deployBridgedTokenBeacon } from "./deployBridgedTokenBeacon";
+import { SupportedChainIds } from "../supportedNetworks";
+
+export async function deployTokenBridge(messageServiceAddress: string, verbose = false) {
+ const [owner] = await ethers.getSigners();
+ const chainIds = [SupportedChainIds.GOERLI, SupportedChainIds.LINEA_TESTNET];
+
+ // Deploy beacon for bridged tokens
+ const tokenBeacons = await deployBridgedTokenBeacon(verbose);
+
+ // Deploying TokenBridges
+ const TokenBridgeFactory = await ethers.getContractFactory("TokenBridge");
+
+ const l1TokenBridge = await upgrades.deployProxy(TokenBridgeFactory, [
+ owner.address,
+ messageServiceAddress,
+ tokenBeacons.l1TokenBeacon.address,
+ chainIds[0],
+ chainIds[1],
+ [], // Reseved Addresses
+ ]);
+ await l1TokenBridge.deployed();
+ if (verbose) {
+ console.log("L1TokenBridge deployed, at address:", l1TokenBridge.address);
+ }
+
+ const l2TokenBridge = await upgrades.deployProxy(TokenBridgeFactory, [
+ owner.address,
+ messageServiceAddress,
+ tokenBeacons.l2TokenBeacon.address,
+ chainIds[1],
+ chainIds[0],
+ [], // Reseved Addresses
+ ]);
+ await l2TokenBridge.deployed();
+ if (verbose) {
+ console.log("L2TokenBridge deployed, at address:", l2TokenBridge.address);
+ }
+
+ // Setting reciprocal addresses of TokenBridges
+ await l1TokenBridge.setRemoteTokenBridge(l2TokenBridge.address);
+ await l2TokenBridge.setRemoteTokenBridge(l1TokenBridge.address);
+ if (verbose) {
+ console.log("Reciprocal addresses of TokenBridges set");
+ }
+
+ if (verbose) {
+ console.log("Deployment finished");
+ }
+
+ return { l1TokenBridge, l2TokenBridge, chainIds, ...tokenBeacons };
+}
+
+export async function deployTokenBridgeWithMockMessaging(verbose = false) {
+ const MessageServiceFactory = await ethers.getContractFactory("MockMessageService");
+
+ // Deploying mock messaging service
+ const messageService = await MessageServiceFactory.deploy();
+ await messageService.deployed();
+
+ const deploymentVars = await deployTokenBridge(messageService.address, verbose);
+ return { messageService, ...deploymentVars };
+}
diff --git a/contracts/scripts/tokenBridge/test/deployTokens.ts b/contracts/scripts/tokenBridge/test/deployTokens.ts
new file mode 100644
index 000000000..1a2eadaa9
--- /dev/null
+++ b/contracts/scripts/tokenBridge/test/deployTokens.ts
@@ -0,0 +1,20 @@
+import { ethers } from "hardhat";
+import { Contract } from "ethers";
+
+const tokenNames = ["L1USDT", "L1DAI", "L1WETH", "L2UNI", "L2SHIBA"];
+
+export async function deployTokens(verbose = false) {
+ const ERC20 = await ethers.getContractFactory("MockERC20MintBurn");
+
+ const tokens: { [name: string]: Contract } = {};
+
+ for (const name of tokenNames) {
+ tokens[name] = await ERC20.deploy(name, name);
+ await tokens[name].deployed();
+ if (verbose) {
+ console.log(name, "deployed");
+ }
+ }
+
+ return tokens;
+}
diff --git a/contracts/scripts/token_bridge/checkTokenBridge.ts b/contracts/scripts/token_bridge/checkTokenBridge.ts
new file mode 100755
index 000000000..01195fee5
--- /dev/null
+++ b/contracts/scripts/token_bridge/checkTokenBridge.ts
@@ -0,0 +1,104 @@
+#!/usr/bin/env node
+
+/*
+Usage:
+ts-node scripts/token_bridge/checkTokenBridge.ts \
+--priv-key YOUR_PRIVATE_KEY \
+--layer l1 \
+--wrapper-address 0x73feE82ba7f6B98D27BCDc2bEFc1d3f6597fb02D \
+--wrapped-address 0x964FF70695da981027c81020B1c58d833D49A640 \
+--l1-blockchain-url L1_URL \
+--l2-blockchain-url L2_URL
+
+Will print out data about the contracts on the selected layer.
+layer options: l1, l2
+*/
+
+import { ethers } from "ethers";
+import log from "npmlog";
+import yargs from "yargs/yargs";
+import { hideBin } from "yargs/helpers";
+import { sanitizeAddress, sanitizePrivKey } from "../cli";
+
+const argv = yargs(hideBin(process.argv))
+ .option("priv-key", {
+ describe: "Your private key",
+ type: "string",
+ demandOption: true,
+ coerce: sanitizePrivKey("priv-key"),
+ })
+ .option("layer", {
+ describe: "Option to check either L1 or L2 token bridge contracts",
+ choices: ["l1", "l2"],
+ type: "string",
+ demandOption: true,
+ })
+ .option("wrapper-address", {
+ describe: "L1 token wrapper smart contract address",
+ type: "string",
+ demandOption: true,
+ coerce: sanitizeAddress("wrapper-address"),
+ })
+ .option("wrapped-address", {
+ describe: "L2 wrapped token smart contract address",
+ type: "string",
+ demandOption: true,
+ coerce: sanitizeAddress("wrapped-address"),
+ })
+ .option("l1-blockchain-url", {
+ describe: "RPC url for the l1 blockchain",
+ type: "string",
+ demandOption: false,
+ })
+ .option("l2-blockchain-url", {
+ describe: "RPC url for the l2 blockchain",
+ type: "string",
+ demandOption: false,
+ })
+ .parseSync();
+
+async function main(args: typeof argv) {
+ const layer = args.layer;
+
+ if (layer === "l1") {
+ // ------------------ L1 token wrapper ------------------
+ const provider = new ethers.providers.JsonRpcProvider(args.l1BlockchainUrl);
+ const wrapperInterface = new ethers.utils.Interface([
+ "function l1Token() public view returns (address)",
+ "function l1Bridge() public view returns (address)",
+ "function l2TokenPair() public view returns (address)",
+ ]);
+ const wallet = new ethers.Wallet(args.privKey, provider);
+ const wrapper = new ethers.Contract(args.wrapperAddress, wrapperInterface, wallet);
+
+ const wrapperL1Token = await wrapper.l1Token();
+ const wrapperl1Bridge = await wrapper.l1Bridge();
+ const wrapperL2TokenPair = await wrapper.l2TokenPair();
+
+ console.log("Wrapper L1 token: " + wrapperL1Token);
+ console.log("Wrapper L1 Bridge: " + wrapperl1Bridge);
+ console.log("Wrapper L2 Token Pair: " + wrapperL2TokenPair);
+ } else {
+ // ------------------ L2 Wrapped token ------------------
+ const provider = new ethers.providers.JsonRpcProvider(argv["l2-blockchain-url"]);
+ const wrappedInterfance = new ethers.utils.Interface([
+ "function erc20Wrapper() public view returns (address)",
+ "function l2Bridge() public view returns (address)",
+ ]);
+ const wallet = new ethers.Wallet(argv.privKey, provider);
+ const wrapped = new ethers.Contract(args.wrappedAddress, wrappedInterfance, wallet);
+
+ const wrappedErc20Wrapper = await wrapped.erc20Wrapper();
+ const wrappedL2Bridge = await wrapped.l2Bridge();
+
+ console.log("wrapped_erc20Wrapper: " + wrappedErc20Wrapper);
+ console.log("wrappedL2Bridge: " + wrappedL2Bridge);
+ }
+}
+
+main(argv)
+ .then(() => process.exit(0))
+ .catch((error) => {
+ log.error("", error);
+ process.exit(1);
+ });
diff --git a/contracts/scripts/token_bridge/contract-deployment-helpers.ts b/contracts/scripts/token_bridge/contract-deployment-helpers.ts
new file mode 100644
index 000000000..8793cf563
--- /dev/null
+++ b/contracts/scripts/token_bridge/contract-deployment-helpers.ts
@@ -0,0 +1,39 @@
+import { ethers } from "hardhat";
+import { JsonRpcProvider } from "@ethersproject/providers";
+import { deployFromFactory } from "../hardhat/utils";
+
+async function deployL1Erc20Wrapper(l1TokenAddress: string, bridgeAddress: string, provider?: JsonRpcProvider) {
+ const erc20Wrapper = await deployFromFactory("ERC20Wrapper", provider, l1TokenAddress, bridgeAddress);
+ console.log(`Contract deployed at ${erc20Wrapper.address}`);
+ return erc20Wrapper;
+}
+
+async function deployL2WrappedToken(
+ bridgeAddress: string,
+ tokenName: string,
+ tokenSymbol: string,
+ provider?: JsonRpcProvider,
+) {
+ const l2WrappedToken = await deployFromFactory("WrappedERC20", provider, bridgeAddress, tokenName, tokenSymbol);
+ console.log(`Contract deployed at ${l2WrappedToken.address}`);
+ return l2WrappedToken;
+}
+
+async function configureTokenWrapper(l1TokenWrapper: string, l2WrappedToken: string, provider: JsonRpcProvider) {
+ const wrapperContract = await ethers.getContractFactory("ERC20Wrapper");
+ wrapperContract.connect(provider.getSigner());
+ const wrapper = wrapperContract.attach(l1TokenWrapper);
+ await wrapper.setL2TokenPair(l2WrappedToken, {
+ maxFeePerGas: 3292893616,
+ maxPriorityFeePerGas: 2500000000,
+ });
+ console.log("Updated wrapper with new token pair address.");
+}
+
+async function deployL1Token(tokenName: string, tokenSymbol: string, provider = null) {
+ const token = await deployFromFactory("Token", provider, tokenName, tokenSymbol);
+ console.log(`Contract deployed at ${token.address}`);
+ return token;
+}
+
+export { deployL1Erc20Wrapper, deployL2WrappedToken, configureTokenWrapper, deployL1Token };
diff --git a/contracts/scripts/token_bridge/deployMockTokenBridge.ts b/contracts/scripts/token_bridge/deployMockTokenBridge.ts
new file mode 100644
index 000000000..7f7102fb7
--- /dev/null
+++ b/contracts/scripts/token_bridge/deployMockTokenBridge.ts
@@ -0,0 +1,26 @@
+import { deployFromFactory } from "../hardhat/utils";
+import { deployL1Erc20Wrapper, deployL2WrappedToken, deployL1Token } from "./contract-deployment-helpers";
+
+async function main() {
+ const tokenName = "USD Coin";
+ const tokenSymbol = "USDC";
+
+ console.log("Deploying L1 Token...");
+ const l1Token = await deployL1Token(tokenName, tokenSymbol);
+
+ console.log("Deploying bridge...");
+ const bridge = await deployFromFactory("MockBridge");
+ console.log(`Contract deployed at ${bridge.address}`);
+
+ console.log("Deploying L1 ERC20 wrapper...");
+ const erc20Wrapper = await deployL1Erc20Wrapper(l1Token.address, bridge.address);
+
+ console.log("Deploying L2 wrapped ERC20 token...");
+ const l2Token = await deployL2WrappedToken(bridge.address, tokenName, tokenSymbol);
+
+ console.log("Setting token pairs...");
+ await erc20Wrapper.setL2TokenPair(l2Token.address);
+ await l2Token.setERC20Wrapper(erc20Wrapper.address);
+}
+
+main().catch((error) => console.error(error));
diff --git a/contracts/scripts/token_bridge/deployUSDC.ts b/contracts/scripts/token_bridge/deployUSDC.ts
new file mode 100755
index 000000000..25dff7d61
--- /dev/null
+++ b/contracts/scripts/token_bridge/deployUSDC.ts
@@ -0,0 +1,79 @@
+#!/usr/bin/env node
+
+/*
+Usage:
+ts-node scripts/token_bridge/deployUSDC.ts \
+--priv-key YOUR_PRIVATE_KEY \
+--l1-bridge-address L1 BRIDGE ADDRESS \
+--l2-bridge-address L2 BRIDGE ADDRESS \
+--l1-token-address L1 TOKEN ADDRESS TO WRAP \
+--l1-blockchain-url L1_URL \
+--l2-blockchain-url L2_URL
+
+Will deploy the token bridge contracts for a given L1 token across both layers.
+layer options: l1, l2
+*/
+
+import { ethers } from "ethers";
+import log from "npmlog";
+import yargs from "yargs/yargs";
+import { hideBin } from "yargs/helpers";
+import { deployL1Erc20Wrapper, deployL2WrappedToken, configureTokenWrapper } from "./contract-deployment-helpers";
+import { sanitizeAddress, sanitizePrivKey } from "../cli";
+
+const argv = yargs(hideBin(process.argv))
+ .option("priv-key", {
+ describe: "Bridge operator private key",
+ type: "string",
+ demandOption: true,
+ coerce: sanitizePrivKey("priv-key"),
+ })
+ .option("l1-bridge-address", {
+ describe: "Address of the Bridge smart contract on L2",
+ type: "string",
+ demandOption: true,
+ coerce: sanitizeAddress("l1-bridge-address"),
+ })
+ .option("l2-bridge-address", {
+ describe: "Address of the Bridge smart contract on L2",
+ type: "string",
+ demandOption: true,
+ coerce: sanitizeAddress("l2-bridge-address"),
+ })
+ .option("l1-token-address", {
+ describe: "Address of the L1 token to wrap",
+ type: "string",
+ demandOption: true,
+ coerce: sanitizeAddress("l1-token-address"),
+ })
+ .option("l1-blockchain-url", {
+ describe: "RPC url for the l1 blockchain",
+ type: "string",
+ demandOption: true,
+ })
+ .option("l2-blockchain-url", {
+ describe: "RPC url for the l2 blockchain",
+ type: "string",
+ demandOption: true,
+ })
+ .parseSync();
+
+async function main(args: typeof argv) {
+ const l1TokenAddress = args.l1TokenAddress;
+ const l1BridgeAddress = args.l1BridgeAddress;
+ const l2BridgeAddress = args.l2BridgeAddress;
+
+ const l1Provider = new ethers.providers.JsonRpcProvider(args.l1BlockchainUrl);
+ const l2Provider = new ethers.providers.JsonRpcProvider(args.l2BlockchainUrl);
+
+ const l1UsdcWrapper = await deployL1Erc20Wrapper(l1TokenAddress, l1BridgeAddress, l1Provider);
+ const l2WrappedUsdc = await deployL2WrappedToken(l2BridgeAddress, "Token Name", "Token Symbol", l2Provider);
+ await configureTokenWrapper(l1UsdcWrapper.address, l2WrappedUsdc.address, l1Provider);
+}
+
+main(argv)
+ .then(() => process.exit(0))
+ .catch((error) => {
+ log.error("", error);
+ process.exit(1);
+ });
diff --git a/contracts/scripts/token_bridge/setTokenBridge.ts b/contracts/scripts/token_bridge/setTokenBridge.ts
new file mode 100755
index 000000000..1a3e40f7a
--- /dev/null
+++ b/contracts/scripts/token_bridge/setTokenBridge.ts
@@ -0,0 +1,142 @@
+#!/usr/bin/env node
+
+/*
+Usage:
+ts-node scripts/token_bridge/setTokenBridge.ts \
+ --priv-key SMART_CONTRACTS_PRIVATE_KEY \
+ --layer l1 \
+ --wrapper-address 0x73feE82ba7f6B98D27BCDc2bEFc1d3f6597fb02D \
+ --wrapper-token-pair 0xnewaddress \
+ --wrapper-pegged-token 0xnewaddress \
+ --wrapped-address 0x964FF70695da981027c81020B1c58d833D49A640 \
+ --wrapped-token-pair 0xnewaddress \
+ --wrapped-l2-bridge 0xnewaddress \
+ --l1-blockchain-url L1_URL \
+ --l2-blockchain-url L2_URL
+
+Used to set data in the contracts on the selected layer, each setter is optional.
+layer options: l1, l2
+*/
+
+import { ethers } from "ethers";
+import log from "npmlog";
+import yargs from "yargs/yargs";
+import { hideBin } from "yargs/helpers";
+import { sanitizeAddress, sanitizePrivKey } from "../cli";
+
+const argv = yargs(hideBin(process.argv))
+ .option("priv-key", {
+ describe: "Smart contract owner private key",
+ type: "string",
+ demandOption: true,
+ coerce: sanitizePrivKey("priv-key"),
+ })
+ .option("layer", {
+ describe: "Option to check either L1 or L2 token bridge contracts",
+ choices: ["l1", "l2"],
+ type: "string",
+ demandOption: true,
+ })
+ .option("wrapper-address", {
+ describe: "L1 token wrapper smart contract address",
+ type: "string",
+ demandOption: true,
+ coerce: sanitizeAddress("wrapper-address"),
+ })
+ .option("wrapper-token-pair", {
+ describe: "L1 token wrapper new L2 token pair address",
+ type: "string",
+ demandOption: false,
+ coerce: sanitizeAddress("wrapper-token-pair"),
+ })
+ .option("wrapper-pegged-token", {
+ describe: "L1 token wrapper new pegged token address",
+ type: "string",
+ demandOption: false,
+ coerce: sanitizeAddress("wrapper-pegged-token"),
+ })
+ .option("wrapped-address", {
+ describe: "L2 wrapped token smart contract address",
+ type: "string",
+ demandOption: true,
+ coerce: sanitizeAddress("wrapped-address"),
+ })
+ .option("wrapped-token-pair", {
+ describe: "L2 wrapped token new L1 token pair address",
+ type: "string",
+ demandOption: false,
+ coerce: sanitizeAddress("wrapped-token-pair"),
+ })
+ .option("wrapped-l2-bridge", {
+ describe: "L2 bridge address",
+ type: "string",
+ demandOption: false,
+ coerce: sanitizeAddress("wrapped-l2-bridge"),
+ })
+ .option("l1-blockchain-url", {
+ describe: "RPC url for the l1 blockchain",
+ type: "string",
+ demandOption: false,
+ })
+ .option("l2-blockchain-url", {
+ describe: "RPC url for the l2 blockchain",
+ type: "string",
+ demandOption: false,
+ })
+ .parseSync();
+
+async function main(args: typeof argv) {
+ const layer = args.layer;
+
+ if (layer === "l1") {
+ // ------------------ L1 token wrapper ------------------
+ const provider = new ethers.providers.JsonRpcProvider(args.l1BlockchainUrl);
+ const wrapperInterface = new ethers.utils.Interface([
+ "function setL2TokenPair(address)",
+ "function setPeggedToken(address)",
+ ]);
+ const wallet = new ethers.Wallet(args.privKey, provider);
+ const wrapper = new ethers.Contract(args.wrapperAddress, wrapperInterface, wallet);
+
+ const tokenPair = args.wrapperTokenPair;
+ const peggedToken = args.wrapperPeggedToken;
+
+ if (tokenPair) {
+ await wrapper.setL2TokenPair(tokenPair, {
+ maxFeePerGas: 3292893616,
+ maxPriorityFeePerGas: 2500000000,
+ });
+ }
+ if (peggedToken) {
+ await wrapper.setPeggedToken(peggedToken, {
+ maxFeePerGas: 3292893616,
+ maxPriorityFeePerGas: 2500000000,
+ });
+ }
+ } else {
+ // ------------------ L2 Wrapped token ------------------
+ const provider = new ethers.providers.JsonRpcProvider(args.l2BlockchainUrl);
+ const wrappedInterfance = new ethers.utils.Interface([
+ "function setERC20Wrapper(address)",
+ "function setL2Bridge(address)",
+ ]);
+ const wallet = new ethers.Wallet(args.privKey, provider);
+ const wrapped = new ethers.Contract(args.wrappedAddress, wrappedInterfance, wallet);
+
+ const l1WrapperAddr = args.wrappedTokenPair;
+ const l2BridgeAddr = args.wrappedL2Bridge;
+ if (l1WrapperAddr) {
+ await wrapped.setERC20Wrapper(l1WrapperAddr);
+ }
+ if (l2BridgeAddr) {
+ await wrapped.setERC20Wrapper(l2BridgeAddr);
+ }
+ }
+}
+
+main(argv)
+ .then(() => process.exit(0))
+ .catch((error) => {
+ log.error("", error);
+ process.exit(1);
+ });
diff --git a/contracts/scripts/upgrades/upgradeL2MessageService.ts b/contracts/scripts/upgrades/upgradeL2MessageService.ts
new file mode 100644
index 000000000..5093c6c6a
--- /dev/null
+++ b/contracts/scripts/upgrades/upgradeL2MessageService.ts
@@ -0,0 +1,41 @@
+import { ethers, upgrades } from "hardhat";
+import { requireEnv } from "../hardhat/utils";
+
+// NB: REMEMBER TO RENAME THE EXISTING CONTRACT TO SOMETHING ELSE TO RETAIN
+// THE SAME NAME FOR THE CONTRACT GOING FORWARD
+// THE TWO CONTRACTS MUST BE NAMED DIFFERENTLY
+
+async function main() {
+ const newContractName = requireEnv("NEW_CONTRACT_NAME");
+ const proxyAddress = requireEnv("PROXY_ADDRESS");
+
+ if (!newContractName || !proxyAddress) {
+ throw new Error(`PROXY_ADDRESS and CONTRACT_NAME env variables are undefined.`);
+ }
+ console.log(`Upgrading contract at ${proxyAddress}`);
+
+ const newContract = await ethers.getContractFactory(newContractName);
+
+ try {
+ await upgrades.validateUpgrade(proxyAddress, newContract, {
+ kind: "transparent",
+ });
+
+ await upgrades.upgradeProxy(proxyAddress, newContract, {
+ kind: "transparent",
+ });
+
+ console.log(`Upgraded contract at ${proxyAddress} with a new version of ${newContractName}`);
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ } catch (error: any) {
+ console.error("Failed to upgrade the proxy contract", error.message);
+ throw error;
+ }
+}
+
+main()
+ .then(() => process.exit(0))
+ .catch((error) => {
+ console.error(error);
+ process.exit(1);
+ });
diff --git a/contracts/scripts/upgrades/upgradeZkEVMv2.ts b/contracts/scripts/upgrades/upgradeZkEVMv2.ts
new file mode 100644
index 000000000..194f96556
--- /dev/null
+++ b/contracts/scripts/upgrades/upgradeZkEVMv2.ts
@@ -0,0 +1,57 @@
+import { ethers, upgrades } from "hardhat";
+import { requireEnv } from "../hardhat/utils";
+
+async function main() {
+ const newContractName = requireEnv("NEW_CONTRACT_NAME");
+ const oldContractName = requireEnv("OLD_CONTRACT_NAME");
+ const proxyAddress = requireEnv("PROXY_ADDRESS");
+ const libraryAddress = requireEnv("TRANSACTION_DECODER_ADDRESS");
+ const initialL2BlockNumber = requireEnv("ZKEVMV2_INITIAL_L2_BLOCK_NUMBER");
+ const initialStateRootHash = requireEnv("ZKEVMV2_INITIAL_STATE_ROOT_HASH");
+
+ if (!newContractName || !proxyAddress) {
+ throw new Error(`PROXY_ADDRESS and CONTRACT_NAME env variables are undefined.`);
+ }
+ console.log(`Upgrading contract at ${proxyAddress}`);
+
+ const oldContract = await ethers.getContractFactory(oldContractName, {
+ libraries: {
+ TransactionDecoder: libraryAddress,
+ },
+ });
+ const newContract = await ethers.getContractFactory(newContractName, {
+ libraries: {
+ TransactionDecoder: libraryAddress,
+ },
+ });
+ console.log("Importing contract");
+ await upgrades.forceImport(proxyAddress, oldContract, {
+ kind: "transparent",
+ });
+
+ try {
+ await upgrades.validateUpgrade(proxyAddress, newContract, {
+ kind: "transparent",
+ unsafeAllow: ["external-library-linking"],
+ });
+
+ await upgrades.upgradeProxy(proxyAddress, newContract, {
+ call: { fn: "initializeV2", args: [initialL2BlockNumber, initialStateRootHash] },
+ kind: "transparent",
+ unsafeAllow: ["external-library-linking"],
+ });
+
+ console.log(`Upgraded contract at ${proxyAddress} with a new version of ${newContractName}`);
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ } catch (error: any) {
+ console.error("Failed to upgrade the proxy contract", error.message);
+ throw error;
+ }
+}
+
+main()
+ .then(() => process.exit(0))
+ .catch((error) => {
+ console.error(error);
+ process.exit(1);
+ });
diff --git a/contracts/scripts/upgrades/upgradeZkEVMv2_no_reinitialisation.ts b/contracts/scripts/upgrades/upgradeZkEVMv2_no_reinitialisation.ts
new file mode 100644
index 000000000..22751ca56
--- /dev/null
+++ b/contracts/scripts/upgrades/upgradeZkEVMv2_no_reinitialisation.ts
@@ -0,0 +1,48 @@
+import { ethers, upgrades } from "hardhat";
+import { requireEnv } from "../hardhat/utils";
+
+// NB: REMEMBER TO RENAME THE EXISTING CONTRACT TO SOMETHING ELSE TO RETAIN
+// THE SAME NAME FOR THE CONTRACT GOING FORWARD
+// THE TWO CONTRACTS MUST BE NAMED DIFFERENTLY
+
+async function main() {
+ const newContractName = requireEnv("NEW_CONTRACT_NAME");
+ const oldContractName = requireEnv("OLD_CONTRACT_NAME");
+ const proxyAddress = requireEnv("PROXY_ADDRESS");
+
+ if (!newContractName || !proxyAddress) {
+ throw new Error(`PROXY_ADDRESS and CONTRACT_NAME env variables are undefined.`);
+ }
+ console.log(`Upgrading contract at ${proxyAddress}`);
+
+ const oldContract = await ethers.getContractFactory(oldContractName);
+ const newContract = await ethers.getContractFactory(newContractName);
+
+ console.log("Importing contract");
+ await upgrades.forceImport(proxyAddress, oldContract, {
+ kind: "transparent",
+ });
+
+ try {
+ await upgrades.validateUpgrade(proxyAddress, newContract, {
+ kind: "transparent",
+ });
+
+ await upgrades.upgradeProxy(proxyAddress, newContract, {
+ kind: "transparent",
+ });
+
+ console.log(`Upgraded contract at ${proxyAddress} with a new version of ${newContractName}`);
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ } catch (error: any) {
+ console.error("Failed to upgrade the proxy contract", error.message);
+ throw error;
+ }
+}
+
+main()
+ .then(() => process.exit(0))
+ .catch((error) => {
+ console.error(error);
+ process.exit(1);
+ });
diff --git a/contracts/scripts/upgrades/upgradeZkEVMv2_with_reinitialisation.ts b/contracts/scripts/upgrades/upgradeZkEVMv2_with_reinitialisation.ts
new file mode 100644
index 000000000..bcb311ce2
--- /dev/null
+++ b/contracts/scripts/upgrades/upgradeZkEVMv2_with_reinitialisation.ts
@@ -0,0 +1,47 @@
+import { ethers, upgrades } from "hardhat";
+import { requireEnv } from "../hardhat/utils";
+
+async function main() {
+ const newContractName = requireEnv("NEW_CONTRACT_NAME");
+ const oldContractName = requireEnv("OLD_CONTRACT_NAME");
+ const proxyAddress = requireEnv("PROXY_ADDRESS");
+ const initialL2BlockNumber = requireEnv("ZKEVMV2_INITIAL_L2_BLOCK_NUMBER");
+ const initialStateRootHash = requireEnv("ZKEVMV2_INITIAL_STATE_ROOT_HASH");
+
+ if (!newContractName || !proxyAddress) {
+ throw new Error(`PROXY_ADDRESS and CONTRACT_NAME env variables are undefined.`);
+ }
+ console.log(`Upgrading contract at ${proxyAddress}`);
+
+ const oldContract = await ethers.getContractFactory(oldContractName);
+ const newContract = await ethers.getContractFactory(newContractName);
+
+ console.log("Importing contract");
+ await upgrades.forceImport(proxyAddress, oldContract, {
+ kind: "transparent",
+ });
+
+ try {
+ await upgrades.validateUpgrade(proxyAddress, newContract, {
+ kind: "transparent",
+ });
+
+ await upgrades.upgradeProxy(proxyAddress, newContract, {
+ call: { fn: "initializeV2", args: [initialL2BlockNumber, initialStateRootHash] },
+ kind: "transparent",
+ });
+
+ console.log(`Upgraded contract at ${proxyAddress} with a new version of ${newContractName}`);
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ } catch (error: any) {
+ console.error("Failed to upgrade the proxy contract", error.message);
+ throw error;
+ }
+}
+
+main()
+ .then(() => process.exit(0))
+ .catch((error) => {
+ console.error(error);
+ process.exit(1);
+ });
diff --git a/contracts/scripts/utils.ts b/contracts/scripts/utils.ts
new file mode 100644
index 000000000..a0ad970ad
--- /dev/null
+++ b/contracts/scripts/utils.ts
@@ -0,0 +1,109 @@
+import { readFileSync, access, constants, watch } from "fs";
+import { ethers } from "ethers";
+import { getBlockchainNode } from "../common";
+import { dirname, basename as _basename } from "path";
+
+class LoggingProvider extends ethers.providers.JsonRpcProvider {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ async perform(method: string, parameters: any): Promise {
+ console.log(">>>", method, parameters);
+ return super.perform(method, parameters).then((result) => {
+ console.log("<<<", method, parameters, result);
+ return result;
+ });
+ }
+}
+
+function getProvider() {
+ const blockchainNode = getBlockchainNode();
+ console.log(`Blockchain RPC at ${blockchainNode}`);
+
+ // "If verbose output is needed please set VERBOSE_BLOCKCHAIN_LOG env"
+ if (process.env.VERBOSE_BLOCKCHAIN_LOG) {
+ return new LoggingProvider(blockchainNode);
+ }
+ return new ethers.providers.JsonRpcProvider(blockchainNode);
+}
+
+function getWallet(credentialsFile: string) {
+ const credentials = JSON.parse(readFileSync(credentialsFile, "utf8"));
+ const privateKey = "0x" + credentials.account_key.priv_key;
+ const provider = getProvider();
+
+ return new ethers.Wallet(privateKey, provider);
+}
+
+function getRollupContractData(rollupConfigPath: string) {
+ const contractData = JSON.parse(readFileSync(rollupConfigPath, "utf8"));
+ console.log(`Contract address: ${contractData.address}`);
+ return contractData;
+}
+
+function getRollupContract(rollupConfigPath: string, wallet: ethers.Wallet) {
+ const contractData = getRollupContractData(rollupConfigPath);
+ return new ethers.Contract(contractData.address, contractData.abi, wallet);
+}
+
+// https://stackoverflow.com/a/47764403/995270
+function waitUntilFileExists(filePath: string, timeout: number) {
+ return new Promise(function (resolve, reject) {
+ const timer = setTimeout(function () {
+ watcher.close();
+ reject(new Error("File did not exists and was not created during the timeout."));
+ }, timeout);
+
+ access(filePath, constants.R_OK, function (err) {
+ if (!err) {
+ clearTimeout(timer);
+ watcher.close();
+ resolve();
+ }
+ });
+
+ const dir = dirname(filePath);
+ const basename = _basename(filePath);
+ const watcher = watch(dir, function (eventType, filename) {
+ if (eventType === "rename" && filename === basename) {
+ clearTimeout(timer);
+ watcher.close();
+ resolve();
+ }
+ });
+ });
+}
+
+/**
+ * @param provider ethers Provider instance
+ * @param percentile [0, 100] maxPriorityFeePerGas will be taken from the
+ * previous block's reward distribution's given percentile bucket.
+ * Used to define priority. 0 - minimal reward, inclusion not guaranteed, 100 -
+ * maximum reward in the previous block, inclusion is very likely
+ * @returns {Promise<{maxPriorityFeePerGas: *, maxFeePerGas: *}>}
+ */
+async function get1559Fees(
+ provider: ethers.providers.JsonRpcProvider,
+ percentile = 15,
+): Promise<{ maxPriorityFeePerGas?: ethers.BigNumber; maxFeePerGas?: ethers.BigNumber }> {
+ return provider.send("eth_feeHistory", ["0x1", "latest", [percentile]]).then((feeHistory) => {
+ const maxPriorityFeePerGas = ethers.BigNumber.from(feeHistory.reward[0][0]);
+ const maxFeePerGas = ethers.BigNumber.from(feeHistory.baseFeePerGas[feeHistory.baseFeePerGas.length - 1]).add(
+ maxPriorityFeePerGas,
+ );
+ if (maxFeePerGas.gt(0) && maxPriorityFeePerGas.gt(0)) {
+ return {
+ maxFeePerGas: maxFeePerGas,
+ maxPriorityFeePerGas: maxPriorityFeePerGas,
+ };
+ }
+ return {};
+ });
+}
+
+export {
+ getProvider,
+ getWallet,
+ getRollupContract,
+ getRollupContractData,
+ waitUntilFileExists as checkExistsWithTimeout,
+ get1559Fees,
+};
diff --git a/contracts/test/Codec.ts b/contracts/test/Codec.ts
new file mode 100644
index 000000000..b615c7e89
--- /dev/null
+++ b/contracts/test/Codec.ts
@@ -0,0 +1,37 @@
+import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
+import { expect } from "chai";
+import { Contract } from "ethers";
+import {
+ Add_L1L2_Message_Hashes_Calldata_With_Empty_Array,
+ Add_L1L2_Message_Hashes_Calldata_With_Five_Hashes,
+ Add_L1L2_Message_Hashes_Calldata_With_One_Hash,
+ L1L2_FiveHashes,
+ Single_Item_L1L2_HashArray,
+} from "./utils/constants";
+import { deployFromFactory } from "./utils/deployment";
+
+describe("Codec V2 Library", () => {
+ let contract: Contract;
+
+ async function deployTestCodecV2Fixture() {
+ return deployFromFactory("TestCodecV2");
+ }
+ beforeEach(async () => {
+ contract = await loadFixture(deployTestCodecV2Fixture);
+ });
+
+ describe("Codec Extracts Hashes from addL1L2MessageHashes calldata", () => {
+ it("addL1L2MessageHashes extracts five hash array", async () => {
+ const hashes = await contract.extractHashesTest(Add_L1L2_Message_Hashes_Calldata_With_Five_Hashes);
+ expect(hashes).to.deep.equal(L1L2_FiveHashes);
+ });
+ it("addL1L2MessageHashes extracts one hash in array", async () => {
+ const hashes = await contract.extractHashesTest(Add_L1L2_Message_Hashes_Calldata_With_One_Hash);
+ expect(hashes).to.deep.equal(Single_Item_L1L2_HashArray);
+ });
+ it("addL1L2MessageHashes extracts empty array", async () => {
+ const hashes = await contract.extractHashesTest(Add_L1L2_Message_Hashes_Calldata_With_Empty_Array);
+ expect(hashes).to.be.empty;
+ });
+ });
+});
diff --git a/contracts/test/L1MessageManager.ts b/contracts/test/L1MessageManager.ts
new file mode 100644
index 000000000..a2957076f
--- /dev/null
+++ b/contracts/test/L1MessageManager.ts
@@ -0,0 +1,113 @@
+import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
+import { expect } from "chai";
+import { TestL1MessageManager } from "../typechain-types";
+import {
+ INBOX_STATUS_RECEIVED,
+ INBOX_STATUS_UNKNOWN,
+ OUTBOX_STATUS_RECEIVED,
+ OUTBOX_STATUS_SENT,
+} from "./utils/constants";
+import { deployFromFactory } from "./utils/deployment";
+import { generateKeccak256Hash, generateNKeccak256Hashes } from "./utils/helpers";
+
+describe("L1MessageManager", () => {
+ let l1MessageManager: TestL1MessageManager;
+
+ async function deployTestL1MessageManagerFixture(): Promise {
+ return deployFromFactory("TestL1MessageManager") as Promise;
+ }
+
+ beforeEach(async () => {
+ l1MessageManager = await loadFixture(deployTestL1MessageManagerFixture);
+ });
+
+ describe("Add L2->L1 message hash in 'inboxL2L1MessageStatus' mapping", () => {
+ it("Should revert if the message hash already exists in 'inboxL2L1MessageStatus' mapping", async () => {
+ const messageHash = generateKeccak256Hash("message1");
+ await l1MessageManager.addL2L1MessageHash(messageHash);
+
+ await expect(l1MessageManager.addL2L1MessageHash(messageHash))
+ .to.be.revertedWithCustomError(l1MessageManager, "MessageAlreadyReceived")
+ .withArgs(messageHash);
+ });
+
+ it("Should succeed if message hash does not exist in 'inboxL2L1MessageStatus' mapping", async () => {
+ const messageHash = generateKeccak256Hash("message1");
+ await l1MessageManager.addL2L1MessageHash(messageHash);
+
+ expect(await l1MessageManager.inboxL2L1MessageStatus(messageHash)).to.equal(INBOX_STATUS_RECEIVED);
+ });
+
+ it("Should emit an event 'L2L1MessageHashAddedToInbox' when succeed", async () => {
+ const messageHash = generateKeccak256Hash("message1");
+
+ await expect(l1MessageManager.addL2L1MessageHash(messageHash))
+ .to.emit(l1MessageManager, "L2L1MessageHashAddedToInbox")
+ .withArgs(messageHash);
+ });
+ });
+
+ describe("Update L2->L1 message hash status in 'inboxL2L1MessageStatus' mapping to 'claimed'", () => {
+ it("Should revert if the message hash has not the status 'received' in 'inboxL2L1MessageStatus' mapping", async () => {
+ const messageHash = generateKeccak256Hash("message1");
+
+ await l1MessageManager.addL2L1MessageHash(messageHash);
+ await l1MessageManager.updateL2L1MessageStatusToClaimed(messageHash);
+
+ await expect(l1MessageManager.updateL2L1MessageStatusToClaimed(messageHash)).to.be.revertedWithCustomError(
+ l1MessageManager,
+ "MessageDoesNotExistOrHasAlreadyBeenClaimed",
+ );
+ });
+
+ it("Should succeed if message hash has the status 'received' in 'inboxL2L1MessageStatus' mapping", async () => {
+ const messageHash = generateKeccak256Hash("message1");
+
+ await l1MessageManager.addL2L1MessageHash(messageHash);
+ await l1MessageManager.updateL2L1MessageStatusToClaimed(messageHash);
+
+ expect(await l1MessageManager.inboxL2L1MessageStatus(messageHash)).to.equal(INBOX_STATUS_UNKNOWN);
+ });
+ });
+
+ describe("Add L1->L2 message hash in 'outboxL1L2MessageStatus' mapping", () => {
+ it("Should succeed if the message hash does not exists in 'outboxL1L2MessageStatus' mapping", async () => {
+ const messageHash = generateKeccak256Hash("message1");
+ await l1MessageManager.addL1L2MessageHash(messageHash);
+ expect(await l1MessageManager.outboxL1L2MessageStatus(messageHash)).to.equal(OUTBOX_STATUS_SENT);
+ });
+ });
+
+ describe("Update L1->L2 message hashes in 'outboxL1L2MessageStatus' mapping to 'received'", () => {
+ it("Should revert if one of the message hashes has the status 'unknown'", async () => {
+ const messageHashes = generateNKeccak256Hashes("message", 2);
+
+ await expect(l1MessageManager.updateL1L2MessageStatusToReceived(messageHashes))
+ .to.be.revertedWithCustomError(l1MessageManager, "L1L2MessageNotSent")
+ .withArgs(messageHashes[0]);
+ });
+
+ it("Should succeed if the message hash exists in 'outboxL1L2MessageStatus' mapping and has the status 'sent'", async () => {
+ const messageHashes = generateNKeccak256Hashes("message", 100);
+ for (const messageHash of messageHashes) {
+ await l1MessageManager.addL1L2MessageHash(messageHash);
+ }
+
+ await l1MessageManager.updateL1L2MessageStatusToReceived(messageHashes);
+ for (const messageHash of messageHashes) {
+ expect(await l1MessageManager.outboxL1L2MessageStatus(messageHash)).to.equal(OUTBOX_STATUS_RECEIVED);
+ }
+ });
+
+ it("Should emit an event 'L1L2MessagesReceivedOnL2' when succeed", async () => {
+ const messageHashes = generateNKeccak256Hashes("message", 2);
+ for (const messageHash of messageHashes) {
+ await l1MessageManager.addL1L2MessageHash(messageHash);
+ }
+
+ await expect(l1MessageManager.updateL1L2MessageStatusToReceived(messageHashes))
+ .to.emit(l1MessageManager, "L1L2MessagesReceivedOnL2")
+ .withArgs(messageHashes);
+ });
+ });
+});
diff --git a/contracts/test/L1MessageService.ts b/contracts/test/L1MessageService.ts
new file mode 100644
index 000000000..55180d230
--- /dev/null
+++ b/contracts/test/L1MessageService.ts
@@ -0,0 +1,1007 @@
+import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
+import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
+import { expect } from "chai";
+import { BigNumber } from "ethers";
+import { ethers } from "hardhat";
+import { TestL1MessageService, TestReceivingContract } from "../typechain-types";
+import {
+ DEFAULT_ADMIN_ROLE,
+ EMPTY_CALLDATA,
+ GENERAL_PAUSE_TYPE,
+ INBOX_STATUS_RECEIVED,
+ INBOX_STATUS_UNKNOWN,
+ INITIAL_WITHDRAW_LIMIT,
+ L1_L2_PAUSE_TYPE,
+ L2_L1_PAUSE_TYPE,
+ LOW_NO_REFUND_MESSAGE_FEE,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ ONE_DAY_IN_SECONDS,
+ OUTBOX_STATUS_SENT,
+ PAUSE_MANAGER_ROLE,
+ RATE_LIMIT_SETTER_ROLE,
+} from "./utils/constants";
+import { deployUpgradableFromFactory } from "./utils/deployment";
+import { encodeSendMessage, generateKeccak256Hash } from "./utils/helpers";
+
+describe("L1MessageService", () => {
+ let l1MessageService: TestL1MessageService;
+ let admin: SignerWithAddress;
+ let pauser: SignerWithAddress;
+ let limitSetter: SignerWithAddress;
+ let notAuthorizedAccount: SignerWithAddress;
+ let postmanAddress: SignerWithAddress;
+
+ async function deployTestL1MessageServiceFixture(): Promise {
+ return deployUpgradableFromFactory("TestL1MessageService", [
+ limitSetter.address,
+ pauser.address,
+ ONE_DAY_IN_SECONDS,
+ INITIAL_WITHDRAW_LIMIT,
+ ]) as Promise;
+ }
+
+ beforeEach(async () => {
+ [admin, pauser, limitSetter, notAuthorizedAccount, postmanAddress] = await ethers.getSigners();
+ l1MessageService = await loadFixture(deployTestL1MessageServiceFixture);
+ });
+
+ describe("Initialisation tests", () => {
+ it("Deployer has default admin role", async () => {
+ expect(await l1MessageService.hasRole(DEFAULT_ADMIN_ROLE, admin.address)).to.be.true;
+ });
+
+ it("limitSetter has RATE_LIMIT_SETTER_ROLE", async () => {
+ expect(await l1MessageService.hasRole(RATE_LIMIT_SETTER_ROLE, limitSetter.address)).to.be.true;
+ });
+
+ it("pauser has PAUSE_MANAGER_ROLE", async () => {
+ expect(await l1MessageService.hasRole(PAUSE_MANAGER_ROLE, pauser.address)).to.be.true;
+ });
+
+ it("Should set rate limit and period", async () => {
+ expect(await l1MessageService.periodInSeconds()).to.be.equal(ONE_DAY_IN_SECONDS);
+ expect(await l1MessageService.limitInWei()).to.be.equal(INITIAL_WITHDRAW_LIMIT);
+ });
+
+ it("It should fail when not initializing", async () => {
+ await expect(
+ l1MessageService.tryInitialize(limitSetter.address, pauser.address, ONE_DAY_IN_SECONDS, INITIAL_WITHDRAW_LIMIT),
+ ).to.be.revertedWith("Initializable: contract is not initializing");
+ });
+
+ it("Should initialise nextMessageNumber", async () => {
+ expect(await l1MessageService.nextMessageNumber()).to.be.equal(1);
+ });
+
+ it("Should fail to deploy missing amount", async () => {
+ await expect(
+ deployUpgradableFromFactory("TestL1MessageService", [
+ limitSetter.address,
+ pauser.address,
+ ONE_DAY_IN_SECONDS,
+ 0,
+ ]),
+ ).to.revertedWithCustomError(l1MessageService, "LimitIsZero");
+ });
+
+ it("Should fail to deploy missing limit period", async () => {
+ await expect(
+ deployUpgradableFromFactory("TestL1MessageService", [
+ limitSetter.address,
+ pauser.address,
+ 0,
+ INITIAL_WITHDRAW_LIMIT,
+ ]),
+ ).to.revertedWithCustomError(l1MessageService, "PeriodIsZero");
+ });
+
+ it("Should fail with empty limiter address", async () => {
+ await expect(
+ deployUpgradableFromFactory("TestL1MessageService", [
+ ethers.constants.AddressZero,
+ pauser.address,
+ ONE_DAY_IN_SECONDS,
+ INITIAL_WITHDRAW_LIMIT,
+ ]),
+ ).to.revertedWithCustomError(l1MessageService, "ZeroAddressNotAllowed");
+ });
+
+ it("Should fail with empty pauser address", async () => {
+ await expect(
+ deployUpgradableFromFactory("TestL1MessageService", [
+ limitSetter.address,
+ ethers.constants.AddressZero,
+ ONE_DAY_IN_SECONDS,
+ INITIAL_WITHDRAW_LIMIT,
+ ]),
+ ).to.revertedWithCustomError(l1MessageService, "ZeroAddressNotAllowed");
+ });
+ });
+
+ describe("Send messages", () => {
+ it("Should fail when the fee is higher than the amount sent", async () => {
+ await expect(
+ l1MessageService.connect(admin).canSendMessage(notAuthorizedAccount.address, MESSAGE_FEE, "0x", {
+ value: MESSAGE_FEE.sub(1),
+ }),
+ ).to.be.revertedWithCustomError(l1MessageService, "ValueSentTooLow");
+ });
+
+ it("Should fail when the to address is address 0", async () => {
+ await expect(
+ l1MessageService.connect(admin).canSendMessage(ethers.constants.AddressZero, MESSAGE_FEE, "0x", {
+ value: MESSAGE_FEE,
+ }),
+ ).to.be.revertedWithCustomError(l1MessageService, "ZeroAddressNotAllowed");
+ });
+
+ it("Should send an ether only message with fees emitting the MessageSent event", async () => {
+ const expectedBytes = await encodeSendMessage(
+ l1MessageService.address,
+ notAuthorizedAccount.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ BigNumber.from(1),
+ "0x",
+ );
+ const messageHash = ethers.utils.keccak256(expectedBytes);
+
+ await expect(
+ l1MessageService.connect(admin).canSendMessage(notAuthorizedAccount.address, MESSAGE_FEE, "0x", {
+ value: MESSAGE_FEE.add(MESSAGE_VALUE_1ETH),
+ }),
+ )
+ .to.emit(l1MessageService, "MessageSent")
+ .withArgs(
+ l1MessageService.address,
+ notAuthorizedAccount.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ 1,
+ "0x",
+ messageHash,
+ );
+
+ const messageStatus = await l1MessageService.outboxL1L2MessageStatus(messageHash);
+ expect(messageStatus).to.be.equal(OUTBOX_STATUS_SENT);
+ });
+
+ it("Should send max limit ether only message with no fee emitting the MessageSent event", async () => {
+ const expectedBytes = await encodeSendMessage(
+ l1MessageService.address,
+ notAuthorizedAccount.address,
+ BigNumber.from(0),
+ INITIAL_WITHDRAW_LIMIT,
+ BigNumber.from(1),
+ "0x",
+ );
+ const messageHash = ethers.utils.keccak256(expectedBytes);
+
+ await expect(
+ l1MessageService
+ .connect(admin)
+ .canSendMessage(notAuthorizedAccount.address, 0, "0x", { value: INITIAL_WITHDRAW_LIMIT }),
+ )
+ .to.emit(l1MessageService, "MessageSent")
+ .withArgs(
+ l1MessageService.address,
+ notAuthorizedAccount.address,
+ 0,
+ INITIAL_WITHDRAW_LIMIT,
+ 1,
+ "0x",
+ messageHash,
+ );
+
+ const messageStatus = await l1MessageService.outboxL1L2MessageStatus(messageHash);
+ expect(messageStatus).to.be.equal(OUTBOX_STATUS_SENT);
+ });
+
+ // this is testing to allow even if claim is blocked
+ it("Should send a message even when L2 to L1 communication is paused", async () => {
+ const expectedBytes = await encodeSendMessage(
+ l1MessageService.address,
+ notAuthorizedAccount.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ BigNumber.from(1),
+ "0x",
+ );
+ const messageHash = ethers.utils.keccak256(expectedBytes);
+
+ await l1MessageService.connect(pauser).pauseByType(L2_L1_PAUSE_TYPE);
+
+ await expect(
+ l1MessageService.connect(admin).canSendMessage(notAuthorizedAccount.address, MESSAGE_FEE, "0x", {
+ value: MESSAGE_FEE.add(MESSAGE_VALUE_1ETH),
+ }),
+ )
+ .to.emit(l1MessageService, "MessageSent")
+ .withArgs(
+ l1MessageService.address,
+ notAuthorizedAccount.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ 1,
+ "0x",
+ messageHash,
+ );
+
+ const messageStatus = await l1MessageService.outboxL1L2MessageStatus(messageHash);
+ expect(messageStatus).to.be.equal(OUTBOX_STATUS_SENT);
+ });
+ });
+
+ describe("Claiming messages", () => {
+ it("Should fail when the message hash does not exist", async () => {
+ await expect(
+ l1MessageService.claimMessage(
+ l1MessageService.address,
+ notAuthorizedAccount.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ ethers.constants.AddressZero,
+ "0x",
+ 1,
+ ),
+ ).to.be.revertedWithCustomError(l1MessageService, "MessageDoesNotExistOrHasAlreadyBeenClaimed");
+ });
+
+ it("Should execute the claim message and send fees to recipient, left over fee to destination", async () => {
+ const expectedBytes = await encodeSendMessage(
+ l1MessageService.address,
+ notAuthorizedAccount.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ BigNumber.from(1),
+ "0x",
+ );
+
+ await l1MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
+ await l1MessageService.addL2L1MessageHash(ethers.utils.keccak256(expectedBytes));
+
+ await l1MessageService.claimMessage(
+ l1MessageService.address,
+ notAuthorizedAccount.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ postmanAddress.address,
+ "0x",
+ 1,
+ );
+ });
+
+ it("Should claim message and send the fees when L1 to L2 communication is paused", async () => {
+ const expectedBytes = await encodeSendMessage(
+ l1MessageService.address,
+ notAuthorizedAccount.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ BigNumber.from(1),
+ "0x",
+ );
+
+ await l1MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
+ await l1MessageService.addL2L1MessageHash(ethers.utils.keccak256(expectedBytes));
+
+ // this is for sending only and should not affect claim
+ await l1MessageService.connect(pauser).pauseByType(L1_L2_PAUSE_TYPE);
+
+ await l1MessageService.claimMessage(
+ l1MessageService.address,
+ notAuthorizedAccount.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ postmanAddress.address,
+ "0x",
+ 1,
+ );
+ });
+
+ it("Should execute the claim message and emit the MessageClaimed event", async () => {
+ const expectedBytes = await encodeSendMessage(
+ l1MessageService.address,
+ notAuthorizedAccount.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ BigNumber.from(1),
+ "0x",
+ );
+
+ const messageHash = ethers.utils.keccak256(expectedBytes);
+ await l1MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
+ await l1MessageService.addL2L1MessageHash(messageHash);
+
+ await expect(
+ l1MessageService.claimMessage(
+ l1MessageService.address,
+ notAuthorizedAccount.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ postmanAddress.address,
+ "0x",
+ 1,
+ ),
+ )
+ .to.emit(l1MessageService, "MessageClaimed")
+ .withArgs(messageHash);
+ });
+
+ it("Should fail when the message hash has been claimed", async () => {
+ const expectedBytes = await encodeSendMessage(
+ l1MessageService.address,
+ notAuthorizedAccount.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ BigNumber.from(1),
+ "0x",
+ );
+
+ await l1MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
+ await l1MessageService.addL2L1MessageHash(ethers.utils.keccak256(expectedBytes));
+
+ await l1MessageService.claimMessage(
+ l1MessageService.address,
+ notAuthorizedAccount.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ postmanAddress.address,
+ "0x",
+ 1,
+ );
+ await expect(
+ l1MessageService.claimMessage(
+ l1MessageService.address,
+ notAuthorizedAccount.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ postmanAddress.address,
+ "0x",
+ 1,
+ ),
+ ).to.be.revertedWithCustomError(l1MessageService, "MessageDoesNotExistOrHasAlreadyBeenClaimed");
+ });
+
+ it("Should execute the claim message and send the fees to msg.sender, left over fee to destination", async () => {
+ const expectedBytes = await encodeSendMessage(
+ l1MessageService.address,
+ notAuthorizedAccount.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ BigNumber.from(1),
+ "0x",
+ );
+
+ const expectedSecondBytes = await encodeSendMessage(
+ l1MessageService.address,
+ notAuthorizedAccount.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ BigNumber.from(2),
+ "0x",
+ );
+
+ const destinationBalance = await notAuthorizedAccount.getBalance();
+
+ await l1MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
+ await l1MessageService.addL2L1MessageHash(ethers.utils.keccak256(expectedBytes));
+ await l1MessageService.addL2L1MessageHash(ethers.utils.keccak256(expectedSecondBytes));
+
+ await l1MessageService
+ .connect(admin)
+ .claimMessage(
+ l1MessageService.address,
+ notAuthorizedAccount.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ ethers.constants.AddressZero,
+ "0x",
+ 1,
+ );
+
+ const adminBalance = await admin.getBalance();
+
+ await l1MessageService
+ .connect(admin)
+ .claimMessage(
+ l1MessageService.address,
+ notAuthorizedAccount.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ ethers.constants.AddressZero,
+ "0x",
+ 2,
+ );
+
+ expect(await notAuthorizedAccount.getBalance()).to.be.greaterThan(
+ destinationBalance.add(MESSAGE_VALUE_1ETH).add(MESSAGE_VALUE_1ETH),
+ );
+ expect(await admin.getBalance()).to.be.greaterThan(adminBalance);
+
+ expect(await l1MessageService.inboxL2L1MessageStatus(ethers.utils.keccak256(expectedBytes))).to.be.equal(
+ INBOX_STATUS_UNKNOWN,
+ );
+ });
+
+ it("Should execute the claim message and send the fees to msg.sender and NOT refund the destination", async () => {
+ const expectedBytes = await encodeSendMessage(
+ l1MessageService.address,
+ notAuthorizedAccount.address,
+ LOW_NO_REFUND_MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ BigNumber.from(1),
+ "0x",
+ );
+ const destinationBalance = await notAuthorizedAccount.getBalance();
+
+ await l1MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
+ await l1MessageService.addL2L1MessageHash(ethers.utils.keccak256(expectedBytes));
+
+ await l1MessageService
+ .connect(admin)
+ .claimMessage(
+ l1MessageService.address,
+ notAuthorizedAccount.address,
+ LOW_NO_REFUND_MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ ethers.constants.AddressZero,
+ "0x",
+ 1,
+ { gasPrice: 1000000000 },
+ );
+
+ expect(await notAuthorizedAccount.getBalance()).to.be.equal(destinationBalance.add(MESSAGE_VALUE_1ETH));
+
+ expect(await l1MessageService.inboxL2L1MessageStatus(ethers.utils.keccak256(expectedBytes))).to.be.equal(
+ INBOX_STATUS_UNKNOWN,
+ );
+ });
+
+ it("Should execute the claim message and send fees to recipient contract and no refund sent", async () => {
+ const factory = await ethers.getContractFactory("TestReceivingContract");
+ const testContract = (await factory.deploy()) as TestReceivingContract;
+
+ const expectedBytes = await encodeSendMessage(
+ l1MessageService.address,
+ testContract.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ BigNumber.from(1),
+ "0x",
+ );
+
+ await l1MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
+ await l1MessageService.addL2L1MessageHash(ethers.utils.keccak256(expectedBytes));
+
+ const adminBalance = await admin.getBalance();
+ await l1MessageService
+ .connect(admin)
+ .claimMessage(
+ l1MessageService.address,
+ testContract.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ ethers.constants.AddressZero,
+ "0x",
+ 1,
+ );
+
+ expect(await ethers.provider.getBalance(testContract.address)).to.be.equal(MESSAGE_VALUE_1ETH);
+ expect(await admin.getBalance()).to.be.greaterThan(adminBalance);
+
+ expect(await l1MessageService.inboxL2L1MessageStatus(ethers.utils.keccak256(expectedBytes))).to.be.equal(
+ INBOX_STATUS_UNKNOWN,
+ );
+ });
+
+ it("Should execute the claim message and send fees to EOA with calldata and no refund sent", async () => {
+ const expectedBytes = await encodeSendMessage(
+ l1MessageService.address,
+ notAuthorizedAccount.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ BigNumber.from(1),
+ "0x123456789a",
+ );
+
+ const startingBalance = await notAuthorizedAccount.getBalance();
+ await l1MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
+ await l1MessageService.addL2L1MessageHash(ethers.utils.keccak256(expectedBytes));
+
+ const adminBalance = await admin.getBalance();
+ await l1MessageService
+ .connect(admin)
+ .claimMessage(
+ l1MessageService.address,
+ notAuthorizedAccount.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ ethers.constants.AddressZero,
+ "0x123456789a",
+ 1,
+ );
+
+ expect(await notAuthorizedAccount.getBalance()).to.be.equal(startingBalance.add(MESSAGE_VALUE_1ETH));
+ expect(await admin.getBalance()).to.be.greaterThan(adminBalance);
+
+ expect(await l1MessageService.inboxL2L1MessageStatus(ethers.utils.keccak256(expectedBytes))).to.be.equal(
+ INBOX_STATUS_UNKNOWN,
+ );
+ });
+
+ it("Should execute the claim message and no fees to EOA with calldata and no refund sent", async () => {
+ const expectedBytes = await encodeSendMessage(
+ l1MessageService.address,
+ notAuthorizedAccount.address,
+ BigNumber.from(0),
+ MESSAGE_VALUE_1ETH,
+ BigNumber.from(1),
+ "0x12",
+ );
+
+ const startingBalance = await notAuthorizedAccount.getBalance();
+ await l1MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
+ await l1MessageService.addL2L1MessageHash(ethers.utils.keccak256(expectedBytes));
+
+ const adminBalance = await admin.getBalance();
+ await l1MessageService
+ .connect(admin)
+ .claimMessage(
+ l1MessageService.address,
+ notAuthorizedAccount.address,
+ BigNumber.from(0),
+ MESSAGE_VALUE_1ETH,
+ ethers.constants.AddressZero,
+ "0x12",
+ 1,
+ );
+
+ expect(await notAuthorizedAccount.getBalance()).to.be.equal(startingBalance.add(MESSAGE_VALUE_1ETH));
+ expect(await admin.getBalance()).to.be.lessThan(adminBalance);
+
+ expect(await l1MessageService.inboxL2L1MessageStatus(ethers.utils.keccak256(expectedBytes))).to.be.equal(
+ INBOX_STATUS_UNKNOWN,
+ );
+ });
+
+ it("Should execute the claim message and no fees to EOA with empty calldata and no refund sent", async () => {
+ const expectedBytes = await encodeSendMessage(
+ l1MessageService.address,
+ notAuthorizedAccount.address,
+ BigNumber.from(0),
+ MESSAGE_VALUE_1ETH,
+ BigNumber.from(1),
+ EMPTY_CALLDATA,
+ );
+
+ const startingBalance = await notAuthorizedAccount.getBalance();
+ await l1MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
+ await l1MessageService.addL2L1MessageHash(ethers.utils.keccak256(expectedBytes));
+
+ const adminBalance = await admin.getBalance();
+ await l1MessageService
+ .connect(admin)
+ .claimMessage(
+ l1MessageService.address,
+ notAuthorizedAccount.address,
+ BigNumber.from(0),
+ MESSAGE_VALUE_1ETH,
+ ethers.constants.AddressZero,
+ EMPTY_CALLDATA,
+ 1,
+ );
+
+ expect(await notAuthorizedAccount.getBalance()).to.be.equal(startingBalance.add(MESSAGE_VALUE_1ETH));
+ expect(await admin.getBalance()).to.be.lessThan(adminBalance);
+
+ expect(await l1MessageService.inboxL2L1MessageStatus(ethers.utils.keccak256(expectedBytes))).to.be.equal(
+ INBOX_STATUS_UNKNOWN,
+ );
+ });
+
+ it("Should execute the claim message when there are no fees", async () => {
+ const expectedBytes = await encodeSendMessage(
+ l1MessageService.address,
+ notAuthorizedAccount.address,
+ BigNumber.from(0),
+ MESSAGE_VALUE_1ETH,
+ BigNumber.from(1),
+ "0x",
+ );
+ const destinationBalance = await notAuthorizedAccount.getBalance();
+
+ await l1MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
+ await l1MessageService.addL2L1MessageHash(ethers.utils.keccak256(expectedBytes));
+
+ const adminBalance = await admin.getBalance();
+ await l1MessageService
+ .connect(admin)
+ .claimMessage(
+ l1MessageService.address,
+ notAuthorizedAccount.address,
+ 0,
+ MESSAGE_VALUE_1ETH,
+ ethers.constants.AddressZero,
+ "0x",
+ 1,
+ );
+
+ expect(await notAuthorizedAccount.getBalance()).to.be.equal(destinationBalance.add(MESSAGE_VALUE_1ETH));
+ expect(await admin.getBalance()).to.be.lessThan(adminBalance);
+
+ expect(await l1MessageService.inboxL2L1MessageStatus(ethers.utils.keccak256(expectedBytes))).to.be.equal(
+ INBOX_STATUS_UNKNOWN,
+ );
+ });
+
+ it("Should provide the correct origin sender", async () => {
+ const sendCalldata = generateKeccak256Hash("setSender()").substring(0, 10);
+
+ const expectedBytes = await encodeSendMessage(
+ l1MessageService.address,
+ l1MessageService.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ BigNumber.from(1),
+ sendCalldata,
+ );
+ await l1MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
+ await l1MessageService.addL2L1MessageHash(ethers.utils.keccak256(expectedBytes));
+
+ const storedSenderBeforeSending = await l1MessageService.originalSender();
+ expect(storedSenderBeforeSending).to.be.equal(ethers.constants.AddressZero);
+
+ await expect(
+ l1MessageService
+ .connect(admin)
+ .claimMessage(
+ l1MessageService.address,
+ l1MessageService.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ ethers.constants.AddressZero,
+ sendCalldata,
+ 1,
+ ),
+ ).to.not.be.reverted;
+
+ const newSender = await l1MessageService.originalSender();
+
+ expect(newSender).to.be.equal(l1MessageService.address);
+ });
+
+ it("Should allow sending post claiming a message", async () => {
+ const sendCalldata = generateKeccak256Hash("sendNewMessage()").substring(0, 10);
+
+ const expectedBytes = await encodeSendMessage(
+ l1MessageService.address,
+ l1MessageService.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ BigNumber.from(1),
+ sendCalldata,
+ );
+ await l1MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
+ await l1MessageService.addL2L1MessageHash(ethers.utils.keccak256(expectedBytes));
+
+ await expect(
+ l1MessageService
+ .connect(admin)
+ .claimMessage(
+ l1MessageService.address,
+ l1MessageService.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ ethers.constants.AddressZero,
+ sendCalldata,
+ 1,
+ ),
+ ).to.not.be.reverted;
+ });
+
+ it("Should fail on reentry when sending to recipient", async () => {
+ const callSignature = generateKeccak256Hash("doReentry()").substring(0, 10);
+
+ const expectedBytes = await encodeSendMessage(
+ l1MessageService.address,
+ l1MessageService.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ BigNumber.from(1),
+ callSignature,
+ );
+ await l1MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
+ await l1MessageService.addL2L1MessageHash(ethers.utils.keccak256(expectedBytes));
+
+ await expect(
+ l1MessageService
+ .connect(admin)
+ .claimMessage(
+ l1MessageService.address,
+ l1MessageService.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ ethers.constants.AddressZero,
+ callSignature,
+ BigNumber.from(1),
+ ),
+ ).to.be.revertedWith("ReentrancyGuard: reentrant call");
+ });
+
+ it("Should fail when the destination errors", async () => {
+ const expectedBytes = await encodeSendMessage(
+ l1MessageService.address,
+ l1MessageService.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ BigNumber.from(1),
+ "0x",
+ );
+
+ await l1MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
+ await l1MessageService.addL2L1MessageHash(ethers.utils.keccak256(expectedBytes));
+ await expect(
+ l1MessageService
+ .connect(admin)
+ .claimMessage(
+ l1MessageService.address,
+ l1MessageService.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ ethers.constants.AddressZero,
+ "0x",
+ 1,
+ ),
+ )
+ .to.be.revertedWithCustomError(l1MessageService, "MessageSendingFailed")
+ .withArgs(l1MessageService.address);
+
+ expect(await l1MessageService.inboxL2L1MessageStatus(ethers.utils.keccak256(expectedBytes))).to.be.equal(
+ INBOX_STATUS_RECEIVED,
+ );
+ });
+
+ it("Should fail when the fee recipient fails errors", async () => {
+ const expectedBytes = await encodeSendMessage(
+ l1MessageService.address,
+ admin.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ BigNumber.from(1),
+ "0x",
+ );
+
+ await l1MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
+ await l1MessageService.addL2L1MessageHash(ethers.utils.keccak256(expectedBytes));
+
+ await expect(
+ l1MessageService
+ .connect(admin)
+ .claimMessage(
+ l1MessageService.address,
+ admin.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ l1MessageService.address,
+ "0x",
+ 1,
+ ),
+ )
+ .to.be.revertedWithCustomError(l1MessageService, "FeePaymentFailed")
+ .withArgs(l1MessageService.address);
+
+ expect(await l1MessageService.inboxL2L1MessageStatus(ethers.utils.keccak256(expectedBytes))).to.be.equal(
+ INBOX_STATUS_RECEIVED,
+ );
+ });
+
+ it("Should revert with send over max limit amount only", async () => {
+ await setHash(BigNumber.from(0), INITIAL_WITHDRAW_LIMIT.add(1));
+
+ await expect(
+ l1MessageService.claimMessage(
+ l1MessageService.address,
+ notAuthorizedAccount.address,
+ 0,
+ INITIAL_WITHDRAW_LIMIT.add(1),
+ postmanAddress.address,
+ "0x",
+ 1,
+ ),
+ ).to.revertedWithCustomError(l1MessageService, "RateLimitExceeded");
+ });
+
+ it("Should revert with send over max limit amount and fees", async () => {
+ await setHash(BigNumber.from(1), INITIAL_WITHDRAW_LIMIT.add(1));
+
+ await expect(
+ l1MessageService.claimMessage(
+ l1MessageService.address,
+ notAuthorizedAccount.address,
+ 1,
+ INITIAL_WITHDRAW_LIMIT.add(1),
+ postmanAddress.address,
+ "0x",
+ 1,
+ ),
+ ).to.revertedWithCustomError(l1MessageService, "RateLimitExceeded");
+ });
+
+ it("Should revert with send over max limit amount and fees - multi tx", async () => {
+ await setHashAndClaimMessage(MESSAGE_FEE, MESSAGE_VALUE_1ETH);
+
+ await setHash(BigNumber.from(1), INITIAL_WITHDRAW_LIMIT.sub(MESSAGE_VALUE_1ETH).sub(MESSAGE_FEE).add(1));
+
+ // limit - (fee+amount from success tx) + 1 = 1 over limit
+ await expect(
+ l1MessageService.claimMessage(
+ l1MessageService.address,
+ notAuthorizedAccount.address,
+ 1,
+ INITIAL_WITHDRAW_LIMIT.sub(MESSAGE_VALUE_1ETH).sub(MESSAGE_FEE).add(1),
+ postmanAddress.address,
+ "0x",
+ 1,
+ ),
+ ).to.revertedWithCustomError(l1MessageService, "RateLimitExceeded");
+ });
+ });
+
+ describe("Resetting limits", () => {
+ it("Should reset limits as limitSetter", async () => {
+ let usedAmount = await l1MessageService.currentPeriodAmountInWei();
+ expect(usedAmount).to.be.equal(0);
+
+ await setHashAndClaimMessage(MESSAGE_FEE, MESSAGE_VALUE_1ETH);
+
+ usedAmount = await l1MessageService.currentPeriodAmountInWei();
+ expect(usedAmount).to.be.equal(MESSAGE_FEE.add(MESSAGE_VALUE_1ETH));
+
+ await l1MessageService.connect(limitSetter).resetAmountUsedInPeriod();
+ usedAmount = await l1MessageService.currentPeriodAmountInWei();
+ expect(usedAmount).to.be.equal(0);
+ });
+
+ it("Should fail reset limits as non-RATE_LIMIT_SETTER_ROLE", async () => {
+ let usedAmount = await l1MessageService.currentPeriodAmountInWei();
+ expect(usedAmount).to.be.equal(0);
+
+ await setHashAndClaimMessage(MESSAGE_FEE, MESSAGE_VALUE_1ETH);
+
+ usedAmount = await l1MessageService.currentPeriodAmountInWei();
+ expect(usedAmount).to.be.equal(MESSAGE_FEE.add(MESSAGE_VALUE_1ETH));
+
+ await expect(l1MessageService.connect(admin).resetAmountUsedInPeriod()).to.be.revertedWith(
+ "AccessControl: account " + admin.address.toLowerCase() + " is missing role " + RATE_LIMIT_SETTER_ROLE,
+ );
+
+ usedAmount = await l1MessageService.currentPeriodAmountInWei();
+ expect(usedAmount).to.be.equal(MESSAGE_FEE.add(MESSAGE_VALUE_1ETH));
+ });
+ });
+
+ describe("Pausing contracts", () => {
+ it("Should fail general pausing as non-pauser", async () => {
+ expect(await l1MessageService.pauseTypeStatuses(GENERAL_PAUSE_TYPE)).to.be.false;
+
+ await expect(l1MessageService.connect(admin).pauseByType(GENERAL_PAUSE_TYPE)).to.be.revertedWith(
+ "AccessControl: account " + admin.address.toLowerCase() + " is missing role " + PAUSE_MANAGER_ROLE,
+ );
+
+ expect(await l1MessageService.pauseTypeStatuses(GENERAL_PAUSE_TYPE)).to.be.false;
+ });
+
+ it("Should pause generally as pause manager", async () => {
+ expect(await l1MessageService.pauseTypeStatuses(GENERAL_PAUSE_TYPE)).to.be.false;
+
+ await l1MessageService.connect(pauser).pauseByType(GENERAL_PAUSE_TYPE);
+
+ expect(await l1MessageService.pauseTypeStatuses(GENERAL_PAUSE_TYPE)).to.be.true;
+ });
+
+ it("Should fail when to claim the contract is generally paused", async () => {
+ await l1MessageService.connect(pauser).pauseByType(GENERAL_PAUSE_TYPE);
+
+ await expect(
+ l1MessageService.claimMessage(
+ l1MessageService.address,
+ notAuthorizedAccount.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ ethers.constants.AddressZero,
+ "0x",
+ 1,
+ ),
+ )
+ .to.be.revertedWithCustomError(l1MessageService, "IsPaused")
+ .withArgs(GENERAL_PAUSE_TYPE);
+ });
+
+ it("Should fail to claim when the L2 to L1 communication is paused", async () => {
+ await l1MessageService.connect(pauser).pauseByType(L2_L1_PAUSE_TYPE);
+
+ await expect(
+ l1MessageService.claimMessage(
+ l1MessageService.address,
+ notAuthorizedAccount.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ ethers.constants.AddressZero,
+ "0x",
+ 1,
+ ),
+ )
+ .to.be.revertedWithCustomError(l1MessageService, "IsPaused")
+ .withArgs(L2_L1_PAUSE_TYPE);
+ });
+
+ it("Should fail to send if the contract is generally paused", async () => {
+ await l1MessageService.connect(pauser).pauseByType(GENERAL_PAUSE_TYPE);
+
+ await expect(
+ l1MessageService
+ .connect(admin)
+ .canSendMessage(notAuthorizedAccount.address, 0, "0x", { value: INITIAL_WITHDRAW_LIMIT }),
+ )
+ .to.be.revertedWithCustomError(l1MessageService, "IsPaused")
+ .withArgs(GENERAL_PAUSE_TYPE);
+
+ const usedAmount = await l1MessageService.currentPeriodAmountInWei();
+ expect(usedAmount).to.be.equal(0);
+ });
+
+ it("Should fail to send if L1 to L2 communication is paused", async () => {
+ await l1MessageService.connect(pauser).pauseByType(L1_L2_PAUSE_TYPE);
+
+ await expect(
+ l1MessageService
+ .connect(admin)
+ .canSendMessage(notAuthorizedAccount.address, 0, "0x", { value: INITIAL_WITHDRAW_LIMIT }),
+ )
+ .to.be.revertedWithCustomError(l1MessageService, "IsPaused")
+ .withArgs(L1_L2_PAUSE_TYPE);
+
+ const usedAmount = await l1MessageService.currentPeriodAmountInWei();
+ expect(usedAmount).to.be.equal(0);
+ });
+ });
+
+ async function setHash(fee: BigNumber, value: BigNumber) {
+ const expectedBytes = await encodeSendMessage(
+ l1MessageService.address,
+ notAuthorizedAccount.address,
+ fee,
+ value,
+ BigNumber.from(1),
+ "0x",
+ );
+
+ await l1MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
+ await l1MessageService.addL2L1MessageHash(ethers.utils.keccak256(expectedBytes));
+ }
+
+ async function setHashAndClaimMessage(fee: BigNumber, value: BigNumber) {
+ const expectedBytes = await encodeSendMessage(
+ l1MessageService.address,
+ notAuthorizedAccount.address,
+ fee,
+ value,
+ BigNumber.from(1),
+ "0x",
+ );
+
+ await l1MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
+ await l1MessageService.addL2L1MessageHash(ethers.utils.keccak256(expectedBytes));
+
+ await l1MessageService.claimMessage(
+ l1MessageService.address,
+ notAuthorizedAccount.address,
+ fee,
+ value,
+ postmanAddress.address,
+ "0x",
+ 1,
+ );
+ }
+});
diff --git a/contracts/test/L2MessageManager.ts b/contracts/test/L2MessageManager.ts
new file mode 100644
index 000000000..42ec9f383
--- /dev/null
+++ b/contracts/test/L2MessageManager.ts
@@ -0,0 +1,119 @@
+import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
+import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
+import { expect } from "chai";
+import { ethers } from "hardhat";
+import { TestL2MessageManager } from "../typechain-types";
+import {
+ DEFAULT_ADMIN_ROLE,
+ INBOX_STATUS_CLAIMED,
+ INBOX_STATUS_RECEIVED,
+ L1_L2_MESSAGE_SETTER_ROLE,
+} from "./utils/constants";
+import { deployUpgradableFromFactory } from "./utils/deployment";
+import { generateKeccak256Hash, generateNKeccak256Hashes } from "./utils/helpers";
+
+describe("L2MessageManager", () => {
+ let l2MessageManager: TestL2MessageManager;
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ let admin: SignerWithAddress;
+ let pauser: SignerWithAddress;
+ let l1l2MessageSetter: SignerWithAddress;
+ let notAuthorizedAccount: SignerWithAddress;
+
+ async function deployL2MessageManagerFixture() {
+ return deployUpgradableFromFactory("TestL2MessageManager", [
+ pauser.address,
+ l1l2MessageSetter.address,
+ ]) as Promise;
+ }
+
+ beforeEach(async () => {
+ [admin, pauser, l1l2MessageSetter, notAuthorizedAccount] = await ethers.getSigners();
+ l2MessageManager = await loadFixture(deployL2MessageManagerFixture);
+ });
+
+ describe("Initialization checks", () => {
+ it("Deployer has default admin role", async () => {
+ expect(await l2MessageManager.hasRole(DEFAULT_ADMIN_ROLE, admin.address)).to.be.true;
+ });
+
+ it("It should fail when not initializing", async () => {
+ await expect(l2MessageManager.tryInitialize(admin.address)).to.be.revertedWith(
+ "Initializable: contract is not initializing",
+ );
+ });
+ });
+
+ describe("Add L1->L2 message hashes in 'inboxL1L2MessageStatus'", () => {
+ it("Should revert if the caller does not have the role 'L1_L2_MESSAGE_SETTER_ROLE'", async () => {
+ const messageHashes = generateNKeccak256Hashes("message", 2);
+
+ await expect(
+ l2MessageManager.connect(notAuthorizedAccount).addL1L2MessageHashes(messageHashes),
+ ).to.be.revertedWith(
+ `AccessControl: account ${notAuthorizedAccount.address.toLowerCase()} is missing role ${L1_L2_MESSAGE_SETTER_ROLE}`,
+ );
+ });
+
+ it("Should revert if message hashes array length is higher than one hundred", async () => {
+ const messageHashes = generateNKeccak256Hashes("message", 101);
+
+ await expect(
+ l2MessageManager.connect(l1l2MessageSetter).addL1L2MessageHashes(messageHashes),
+ ).to.be.revertedWithCustomError(l2MessageManager, "MessageHashesListLengthHigherThanOneHundred");
+ });
+
+ it("Should succeed if message hashes array param is correct", async () => {
+ const messageHashes = generateNKeccak256Hashes("message", 100);
+ await l2MessageManager.connect(l1l2MessageSetter).addL1L2MessageHashes(messageHashes);
+ for (const messageHash of messageHashes) {
+ expect(await l2MessageManager.connect(l1l2MessageSetter).inboxL1L2MessageStatus(messageHash)).to.equal(
+ INBOX_STATUS_RECEIVED,
+ );
+ }
+ });
+
+ it("Should succeed if duplicates hashes exist in the array", async () => {
+ const messageHashes = [
+ ethers.constants.HashZero,
+ ethers.constants.HashZero,
+ generateKeccak256Hash("message1"),
+ generateKeccak256Hash("message1"),
+ ];
+ await l2MessageManager.connect(l1l2MessageSetter).addL1L2MessageHashes(messageHashes);
+ for (const messageHash of messageHashes) {
+ expect(await l2MessageManager.connect(l1l2MessageSetter).inboxL1L2MessageStatus(messageHash)).to.equal(
+ INBOX_STATUS_RECEIVED,
+ );
+ }
+ });
+
+ it("Should emit an event 'L1L2MessageHashesAddedToInbox' when succeed", async () => {
+ const messageHashes = generateNKeccak256Hashes("message", 50);
+
+ await expect(l2MessageManager.connect(l1l2MessageSetter).addL1L2MessageHashes(messageHashes))
+ .to.emit(l2MessageManager, "L1L2MessageHashesAddedToInbox")
+ .withArgs(messageHashes);
+ });
+ });
+
+ describe("Update L1->L2 message status to 'claimed' in 'inboxL1L2MessageStatus'", () => {
+ it("Should revert if the message hash has not the status 'received' in 'inboxL1L2MessageStatus' mapping", async () => {
+ const messageHash = generateKeccak256Hash("message");
+ await expect(l2MessageManager.updateL1L2MessageStatusToClaimed(messageHash)).to.be.revertedWithCustomError(
+ l2MessageManager,
+ "MessageDoesNotExistOrHasAlreadyBeenClaimed",
+ );
+ });
+
+ it("Should succeed if message hash has the status 'received' in 'inboxL1L2MessageStatus' mapping", async () => {
+ const messageHash = generateKeccak256Hash("message1");
+ const messageHashes = generateNKeccak256Hashes("message", 50);
+
+ await l2MessageManager.connect(l1l2MessageSetter).addL1L2MessageHashes(messageHashes);
+ await l2MessageManager.updateL1L2MessageStatusToClaimed(messageHash);
+
+ expect(await l2MessageManager.inboxL1L2MessageStatus(messageHash)).to.equal(INBOX_STATUS_CLAIMED);
+ });
+ });
+});
diff --git a/contracts/test/L2MessageService.ts b/contracts/test/L2MessageService.ts
new file mode 100644
index 000000000..0a5b89060
--- /dev/null
+++ b/contracts/test/L2MessageService.ts
@@ -0,0 +1,1158 @@
+import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
+import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
+import { expect } from "chai";
+import { BigNumber } from "ethers";
+import { ethers } from "hardhat";
+import { TestL2MessageService, TestReceivingContract } from "../typechain-types";
+import {
+ BLOCK_COINBASE,
+ DEFAULT_ADMIN_ROLE,
+ EMPTY_CALLDATA,
+ GENERAL_PAUSE_TYPE,
+ INBOX_STATUS_CLAIMED,
+ INBOX_STATUS_RECEIVED,
+ INITIAL_WITHDRAW_LIMIT,
+ L1_L2_MESSAGE_SETTER_ROLE,
+ L1_L2_PAUSE_TYPE,
+ L2_L1_PAUSE_TYPE,
+ LOW_NO_REFUND_MESSAGE_FEE,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ MINIMUM_FEE,
+ MINIMUM_FEE_SETTER_ROLE,
+ ONE_DAY_IN_SECONDS,
+ PAUSE_MANAGER_ROLE,
+ RATE_LIMIT_SETTER_ROLE,
+} from "./utils/constants";
+import { deployUpgradableFromFactory } from "./utils/deployment";
+import { encodeSendMessage, generateKeccak256Hash } from "./utils/helpers";
+
+describe("L2MessageService", () => {
+ let l2MessageService: TestL2MessageService;
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ let admin: SignerWithAddress;
+ let securityCouncil: SignerWithAddress;
+ let l1l2MessageSetter: SignerWithAddress;
+ let notAuthorizedAccount: SignerWithAddress;
+ let postmanAddress: SignerWithAddress;
+
+ async function deployL2MessageServiceFixture() {
+ return deployUpgradableFromFactory("TestL2MessageService", [
+ securityCouncil.address,
+ l1l2MessageSetter.address,
+ 86400,
+ INITIAL_WITHDRAW_LIMIT,
+ ]) as Promise;
+ }
+
+ beforeEach(async () => {
+ [admin, securityCouncil, l1l2MessageSetter, notAuthorizedAccount, postmanAddress] = await ethers.getSigners();
+ l2MessageService = await loadFixture(deployL2MessageServiceFixture);
+ });
+
+ describe("Initialization checks", () => {
+ it("Security council should have DEFAULT_ADMIN_ROLE", async () => {
+ expect(await l2MessageService.hasRole(DEFAULT_ADMIN_ROLE, securityCouncil.address)).to.be.true;
+ });
+
+ it("Security council should have MINIMUM_FEE_SETTER_ROLE", async () => {
+ expect(await l2MessageService.hasRole(MINIMUM_FEE_SETTER_ROLE, securityCouncil.address)).to.be.true;
+ });
+
+ it("Security council should have RATE_LIMIT_SETTER_ROLE role", async () => {
+ expect(await l2MessageService.hasRole(RATE_LIMIT_SETTER_ROLE, securityCouncil.address)).to.be.true;
+ });
+
+ it("Security council should have PAUSE_MANAGER_ROLE", async () => {
+ expect(await l2MessageService.hasRole(PAUSE_MANAGER_ROLE, securityCouncil.address)).to.be.true;
+ });
+
+ it("L1->L2 message setter should have L1_L2_MESSAGE_SETTER_ROLE role", async () => {
+ expect(await l2MessageService.hasRole(L1_L2_MESSAGE_SETTER_ROLE, l1l2MessageSetter.address)).to.be.true;
+ });
+
+ it("Should initialise nextMessageNumber", async () => {
+ expect(await l2MessageService.nextMessageNumber()).to.be.equal(1);
+ });
+
+ it("Should set rate limit and period", async () => {
+ expect(await l2MessageService.periodInSeconds()).to.be.equal(ONE_DAY_IN_SECONDS);
+ expect(await l2MessageService.limitInWei()).to.be.equal(INITIAL_WITHDRAW_LIMIT);
+ });
+
+ it("Should fail to deploy missing limit amount", async () => {
+ await expect(
+ deployUpgradableFromFactory("TestL2MessageService", [
+ securityCouncil.address,
+ l1l2MessageSetter.address,
+ 86400,
+ 0,
+ ]),
+ ).to.be.revertedWithCustomError(l2MessageService, "LimitIsZero");
+ });
+
+ it("Should fail to deploy missing period", async () => {
+ await expect(
+ deployUpgradableFromFactory("TestL2MessageService", [
+ securityCouncil.address,
+ l1l2MessageSetter.address,
+ 0,
+ MESSAGE_VALUE_1ETH.add(MESSAGE_VALUE_1ETH),
+ ]),
+ ).to.be.revertedWithCustomError(l2MessageService, "PeriodIsZero");
+ });
+
+ it("Should fail with empty securityCouncil address", async () => {
+ await expect(
+ deployUpgradableFromFactory("TestL2MessageService", [
+ ethers.constants.AddressZero,
+ l1l2MessageSetter.address,
+ ONE_DAY_IN_SECONDS,
+ INITIAL_WITHDRAW_LIMIT,
+ ]),
+ ).to.be.revertedWithCustomError(l2MessageService, "ZeroAddressNotAllowed");
+ });
+
+ it("Should fail with empty l1l2MessageSetter address", async () => {
+ await expect(
+ deployUpgradableFromFactory("TestL2MessageService", [
+ securityCouncil.address,
+ ethers.constants.AddressZero,
+ ONE_DAY_IN_SECONDS,
+ INITIAL_WITHDRAW_LIMIT,
+ ]),
+ ).to.be.revertedWithCustomError(l2MessageService, "ZeroAddressNotAllowed");
+ });
+
+ it("Should fail on second initialisation", async () => {
+ await expect(
+ l2MessageService.initialize(
+ securityCouncil.address,
+ l1l2MessageSetter.address,
+ ONE_DAY_IN_SECONDS,
+ INITIAL_WITHDRAW_LIMIT,
+ ),
+ ).to.be.revertedWith("Initializable: contract is already initialized");
+ });
+ });
+
+ describe("Send message", () => {
+ describe("When the contract is paused", () => {
+ it("Should fail to send if the contract is paused", async () => {
+ await l2MessageService.connect(securityCouncil).pauseByType(GENERAL_PAUSE_TYPE);
+
+ await expect(
+ l2MessageService
+ .connect(securityCouncil)
+ .sendMessage(notAuthorizedAccount.address, MESSAGE_FEE, EMPTY_CALLDATA, { value: INITIAL_WITHDRAW_LIMIT }),
+ )
+ .to.be.revertedWithCustomError(l2MessageService, "IsPaused")
+ .withArgs(GENERAL_PAUSE_TYPE);
+ });
+ });
+
+ describe("When the L2->L1 messaging service is paused", () => {
+ it("Should fail to send if the L2->L1 messaging service is paused", async () => {
+ await l2MessageService.connect(securityCouncil).pauseByType(L2_L1_PAUSE_TYPE);
+
+ await expect(
+ l2MessageService
+ .connect(securityCouncil)
+ .sendMessage(notAuthorizedAccount.address, MESSAGE_FEE, EMPTY_CALLDATA, { value: INITIAL_WITHDRAW_LIMIT }),
+ )
+ .to.be.revertedWithCustomError(l2MessageService, "IsPaused")
+ .withArgs(L2_L1_PAUSE_TYPE);
+ });
+ });
+
+ describe("When the contract is not paused", () => {
+ it("Should fail when the fee is higher than the amount sent", async () => {
+ await expect(
+ l2MessageService.connect(admin).sendMessage(notAuthorizedAccount.address, MESSAGE_FEE, EMPTY_CALLDATA, {
+ value: MESSAGE_FEE.sub(ethers.utils.parseEther("0.01")),
+ }),
+ ).to.be.revertedWithCustomError(l2MessageService, "ValueSentTooLow");
+ });
+
+ it("Should fail when the coinbase fee transfer fails", async () => {
+ await l2MessageService.connect(securityCouncil).setMinimumFee(MINIMUM_FEE);
+
+ await ethers.provider.send("hardhat_setCoinbase", [l2MessageService.address]);
+
+ await expect(
+ l2MessageService
+ .connect(admin)
+ .sendMessage(notAuthorizedAccount.address, MESSAGE_FEE.add(MINIMUM_FEE), EMPTY_CALLDATA, {
+ value: MINIMUM_FEE.add(MINIMUM_FEE),
+ }),
+ )
+ .to.be.revertedWithCustomError(l2MessageService, "FeePaymentFailed")
+ .withArgs(l2MessageService.address);
+
+ await ethers.provider.send("hardhat_setCoinbase", [BLOCK_COINBASE]);
+ });
+
+ it("Should fail when the minimumFee is higher than the amount sent", async () => {
+ await l2MessageService.connect(securityCouncil).setMinimumFee(MINIMUM_FEE);
+
+ await expect(
+ l2MessageService.connect(admin).sendMessage(notAuthorizedAccount.address, MESSAGE_FEE, EMPTY_CALLDATA, {
+ value: MESSAGE_FEE.add(ethers.utils.parseEther("0.01")),
+ }),
+ ).to.be.revertedWithCustomError(l2MessageService, "FeeTooLow");
+ });
+
+ it("Should fail when the to address is address 0", async () => {
+ await expect(
+ l2MessageService.connect(admin).canSendMessage(ethers.constants.AddressZero, MESSAGE_FEE, "0x", {
+ value: MESSAGE_FEE,
+ }),
+ ).to.be.revertedWithCustomError(l2MessageService, "ZeroAddressNotAllowed");
+ });
+
+ it("Should increase the balance of the coinbase with the minimumFee", async () => {
+ await l2MessageService.connect(securityCouncil).setMinimumFee(MINIMUM_FEE);
+
+ const initialCoinbaseBalance = await ethers.provider.getBalance(BLOCK_COINBASE);
+
+ await l2MessageService
+ .connect(admin)
+ .sendMessage(notAuthorizedAccount.address, MESSAGE_FEE.add(MINIMUM_FEE), EMPTY_CALLDATA, {
+ value: MINIMUM_FEE.add(MESSAGE_FEE),
+ });
+
+ expect(await ethers.provider.getBalance(BLOCK_COINBASE)).to.be.gt(initialCoinbaseBalance.add(MINIMUM_FEE));
+ });
+
+ it("Should succeed if 'MessageSent' event is emitted", async () => {
+ await l2MessageService.connect(securityCouncil).setMinimumFee(MINIMUM_FEE);
+
+ const expectedBytes = await encodeSendMessage(
+ securityCouncil.address,
+ notAuthorizedAccount.address,
+ BigNumber.from(MESSAGE_FEE),
+ MESSAGE_VALUE_1ETH.sub(MESSAGE_FEE).sub(MINIMUM_FEE),
+ BigNumber.from(1),
+ EMPTY_CALLDATA,
+ );
+ const messageHash = ethers.utils.keccak256(expectedBytes);
+
+ await expect(
+ l2MessageService
+ .connect(securityCouncil)
+ .sendMessage(notAuthorizedAccount.address, MESSAGE_FEE.add(MINIMUM_FEE), EMPTY_CALLDATA, {
+ value: MESSAGE_VALUE_1ETH,
+ }),
+ )
+ .to.emit(l2MessageService, "MessageSent")
+ .withArgs(
+ securityCouncil.address,
+ notAuthorizedAccount.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH.sub(MESSAGE_FEE).sub(MINIMUM_FEE),
+ 1,
+ EMPTY_CALLDATA,
+ messageHash,
+ );
+ });
+
+ it("Should send an ether only message with fees emitting the MessageSent event", async () => {
+ const expectedBytes = await encodeSendMessage(
+ admin.address,
+ notAuthorizedAccount.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ BigNumber.from(1),
+ "0x",
+ );
+ const messageHash = ethers.utils.keccak256(expectedBytes);
+
+ await expect(
+ l2MessageService.connect(admin).sendMessage(notAuthorizedAccount.address, MESSAGE_FEE, "0x", {
+ value: MESSAGE_FEE.add(MESSAGE_VALUE_1ETH),
+ }),
+ )
+ .to.emit(l2MessageService, "MessageSent")
+ .withArgs(admin.address, notAuthorizedAccount.address, MESSAGE_FEE, MESSAGE_VALUE_1ETH, 1, "0x", messageHash);
+ });
+
+ it("Should send max limit ether only message with no fee emitting the MessageSent event", async () => {
+ const expectedBytes = await encodeSendMessage(
+ securityCouncil.address,
+ notAuthorizedAccount.address,
+ BigNumber.from(0),
+ INITIAL_WITHDRAW_LIMIT,
+ BigNumber.from(1),
+ "0x",
+ );
+ const messageHash = ethers.utils.keccak256(expectedBytes);
+
+ await expect(
+ l2MessageService
+ .connect(securityCouncil)
+ .sendMessage(notAuthorizedAccount.address, 0, "0x", { value: INITIAL_WITHDRAW_LIMIT }),
+ )
+ .to.emit(l2MessageService, "MessageSent")
+ .withArgs(
+ securityCouncil.address,
+ notAuthorizedAccount.address,
+ 0,
+ INITIAL_WITHDRAW_LIMIT,
+ 1,
+ "0x",
+ messageHash,
+ );
+ });
+
+ it("Should revert with send over max limit amount only", async () => {
+ await expect(
+ l2MessageService
+ .connect(admin)
+ .sendMessage(notAuthorizedAccount.address, 0, "0x", { value: INITIAL_WITHDRAW_LIMIT.add(1) }),
+ ).to.revertedWithCustomError(l2MessageService, "RateLimitExceeded");
+ });
+
+ it("Should revert with send over max limit amount and fees", async () => {
+ await expect(
+ l2MessageService
+ .connect(admin)
+ .sendMessage(notAuthorizedAccount.address, 1, "0x", { value: INITIAL_WITHDRAW_LIMIT.add(1) }),
+ ).to.revertedWithCustomError(l2MessageService, "RateLimitExceeded");
+ });
+
+ it("Should fail when the rate limit would be exceeded - multi transactions", async () => {
+ await l2MessageService
+ .connect(admin)
+ .sendMessage(notAuthorizedAccount.address, MESSAGE_FEE, "0x", { value: MESSAGE_FEE.add(MESSAGE_VALUE_1ETH) });
+
+ const breachingAmount = INITIAL_WITHDRAW_LIMIT.sub(MESSAGE_FEE).sub(MESSAGE_VALUE_1ETH).add(1);
+
+ await expect(
+ l2MessageService
+ .connect(admin)
+ .sendMessage(notAuthorizedAccount.address, 0, "0x", { value: breachingAmount }),
+ ).to.revertedWithCustomError(l2MessageService, "RateLimitExceeded");
+ });
+
+ it("Should not accrue rate limit while sending transaction with coinbaseFee only", async () => {
+ const initialCoinbaseBalance = await ethers.provider.getBalance(BLOCK_COINBASE);
+ await l2MessageService.connect(securityCouncil).setMinimumFee(MINIMUM_FEE);
+
+ const initialRateLimitUsed = await l2MessageService.currentPeriodAmountInWei();
+
+ const expectedBytes = await encodeSendMessage(
+ admin.address,
+ notAuthorizedAccount.address,
+ BigNumber.from(0),
+ BigNumber.from(0),
+ BigNumber.from(1),
+ "0x",
+ );
+ const messageHash = ethers.utils.keccak256(expectedBytes);
+
+ await expect(
+ l2MessageService
+ .connect(admin)
+ .sendMessage(notAuthorizedAccount.address, MINIMUM_FEE, "0x", { value: MINIMUM_FEE }),
+ )
+ .to.emit(l2MessageService, "MessageSent")
+ .withArgs(
+ admin.address,
+ notAuthorizedAccount.address,
+ BigNumber.from(0),
+ BigNumber.from(0),
+ 1,
+ "0x",
+ messageHash,
+ );
+
+ const postCoinbaseBalance = await ethers.provider.getBalance(BLOCK_COINBASE);
+ await expect(postCoinbaseBalance).to.be.gt(initialCoinbaseBalance);
+
+ const postRateLimitUsed = await l2MessageService.currentPeriodAmountInWei();
+ await expect(postRateLimitUsed).to.be.equal(initialRateLimitUsed);
+ });
+
+ it("Should accrue rate limit while sending transaction with 0 value and real fee, postmanFee = fee - coinbaseFee", async () => {
+ const initialCoinbaseBalance = await ethers.provider.getBalance(BLOCK_COINBASE);
+ const initialRateLimitUsed = await l2MessageService.currentPeriodAmountInWei();
+
+ await l2MessageService.connect(securityCouncil).setMinimumFee(MINIMUM_FEE);
+
+ const expectedBytes = await encodeSendMessage(
+ admin.address,
+ notAuthorizedAccount.address,
+ MESSAGE_VALUE_1ETH.sub(MINIMUM_FEE),
+ BigNumber.from(0),
+ BigNumber.from(1),
+ "0x",
+ );
+ const messageHash = ethers.utils.keccak256(expectedBytes);
+
+ await expect(
+ l2MessageService
+ .connect(admin)
+ .sendMessage(notAuthorizedAccount.address, MESSAGE_VALUE_1ETH, "0x", { value: MESSAGE_VALUE_1ETH }),
+ )
+ .to.emit(l2MessageService, "MessageSent")
+ .withArgs(
+ admin.address,
+ notAuthorizedAccount.address,
+ MESSAGE_VALUE_1ETH.sub(MINIMUM_FEE),
+ 0,
+ 1,
+ "0x",
+ messageHash,
+ );
+
+ const postCoinbaseBalance = await ethers.provider.getBalance(BLOCK_COINBASE);
+
+ const postRateLimitUsed = await l2MessageService.currentPeriodAmountInWei();
+
+ await expect(postCoinbaseBalance).to.be.gt(initialCoinbaseBalance);
+ expect(await postRateLimitUsed).to.be.gt(initialRateLimitUsed);
+ });
+
+ it("Should accrue rate limit while sending transaction with value with real fee, postmanFee = fee - coinbaseFee", async () => {
+ const initialCoinbaseBalance = await ethers.provider.getBalance(BLOCK_COINBASE);
+ const initialRateLimitUsed = await l2MessageService.currentPeriodAmountInWei();
+
+ await l2MessageService.connect(securityCouncil).setMinimumFee(MINIMUM_FEE);
+
+ const expectedBytes = await encodeSendMessage(
+ admin.address,
+ notAuthorizedAccount.address,
+ MINIMUM_FEE.add(MESSAGE_FEE).sub(MINIMUM_FEE),
+ MESSAGE_VALUE_1ETH.sub(MINIMUM_FEE.add(MESSAGE_FEE)),
+ BigNumber.from(1),
+ "0x",
+ );
+ const messageHash = ethers.utils.keccak256(expectedBytes);
+
+ await expect(
+ l2MessageService
+ .connect(admin)
+ .sendMessage(notAuthorizedAccount.address, MINIMUM_FEE.add(MESSAGE_FEE), "0x", {
+ value: MESSAGE_VALUE_1ETH,
+ }),
+ )
+ .to.emit(l2MessageService, "MessageSent")
+ .withArgs(
+ admin.address,
+ notAuthorizedAccount.address,
+ MINIMUM_FEE.add(MESSAGE_FEE).sub(MINIMUM_FEE),
+ MESSAGE_VALUE_1ETH.sub(MINIMUM_FEE.add(MESSAGE_FEE)),
+ 1,
+ "0x",
+ messageHash,
+ );
+
+ const postCoinbaseBalance = await ethers.provider.getBalance(BLOCK_COINBASE);
+
+ const postRateLimitUsed = await l2MessageService.currentPeriodAmountInWei();
+
+ await expect(postCoinbaseBalance).to.be.gt(initialCoinbaseBalance);
+ expect(await postRateLimitUsed).to.be.gt(initialRateLimitUsed);
+ });
+
+ it("Should accrue rate limit while sending transaction with value with coinbaseFee, postmanFee = 0", async () => {
+ const initialCoinbaseBalance = await ethers.provider.getBalance(BLOCK_COINBASE);
+ const initialRateLimitUsed = await l2MessageService.currentPeriodAmountInWei();
+
+ await l2MessageService.connect(securityCouncil).setMinimumFee(MINIMUM_FEE);
+
+ const expectedBytes = await encodeSendMessage(
+ admin.address,
+ notAuthorizedAccount.address,
+ BigNumber.from(0),
+ MESSAGE_VALUE_1ETH.sub(MINIMUM_FEE),
+ BigNumber.from(1),
+ "0x",
+ );
+ const messageHash = ethers.utils.keccak256(expectedBytes);
+
+ await expect(
+ l2MessageService
+ .connect(admin)
+ .sendMessage(notAuthorizedAccount.address, MINIMUM_FEE, "0x", { value: MESSAGE_VALUE_1ETH }),
+ )
+ .to.emit(l2MessageService, "MessageSent")
+ .withArgs(
+ admin.address,
+ notAuthorizedAccount.address,
+ BigNumber.from(0),
+ MESSAGE_VALUE_1ETH.sub(MINIMUM_FEE),
+ 1,
+ "0x",
+ messageHash,
+ );
+
+ const postCoinbaseBalance = await ethers.provider.getBalance(BLOCK_COINBASE);
+
+ const postRateLimitUsed = await l2MessageService.currentPeriodAmountInWei();
+
+ await expect(postCoinbaseBalance).to.be.gt(initialCoinbaseBalance);
+ expect(await postRateLimitUsed).to.be.gt(initialRateLimitUsed);
+ });
+ });
+ });
+
+ describe("Claim message", () => {
+ describe("When the contract is paused", async () => {
+ it("Should revert if the contract is paused", async () => {
+ await l2MessageService.connect(securityCouncil).pauseByType(GENERAL_PAUSE_TYPE);
+
+ await expect(
+ l2MessageService
+ .connect(securityCouncil)
+ .claimMessage(
+ securityCouncil.address,
+ notAuthorizedAccount.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ notAuthorizedAccount.address,
+ EMPTY_CALLDATA,
+ 1,
+ ),
+ )
+ .to.be.revertedWithCustomError(l2MessageService, "IsPaused")
+ .withArgs(GENERAL_PAUSE_TYPE);
+ });
+ });
+
+ describe("When L1->L2 messaging service is paused", async () => {
+ it("Should revert if the L1->L2 messaging service is paused", async () => {
+ await l2MessageService.connect(securityCouncil).pauseByType(L1_L2_PAUSE_TYPE);
+
+ await expect(
+ l2MessageService
+ .connect(securityCouncil)
+ .claimMessage(
+ securityCouncil.address,
+ notAuthorizedAccount.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ notAuthorizedAccount.address,
+ EMPTY_CALLDATA,
+ 1,
+ ),
+ )
+ .to.be.revertedWithCustomError(l2MessageService, "IsPaused")
+ .withArgs(L1_L2_PAUSE_TYPE);
+ });
+ });
+
+ describe("When the contract is not paused", () => {
+ it("Should succeed if 'MessageClaimed' event is emitted", async () => {
+ const expectedBytes = await encodeSendMessage(
+ admin.address,
+ notAuthorizedAccount.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ BigNumber.from(1),
+ "0x",
+ );
+
+ await l2MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
+
+ const expectedBytesArray = [ethers.utils.keccak256(expectedBytes)];
+ await l2MessageService.connect(l1l2MessageSetter).addL1L2MessageHashes(expectedBytesArray);
+
+ await expect(
+ l2MessageService.claimMessage(
+ admin.address,
+ notAuthorizedAccount.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ postmanAddress.address,
+ "0x",
+ 1,
+ ),
+ )
+ .to.emit(l2MessageService, "MessageClaimed")
+ .withArgs(ethers.utils.keccak256(expectedBytes));
+ });
+
+ it("Should fail when the message hash does not exist", async () => {
+ await expect(
+ l2MessageService.claimMessage(
+ l2MessageService.address,
+ notAuthorizedAccount.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ ethers.constants.AddressZero,
+ EMPTY_CALLDATA,
+ 1,
+ ),
+ ).to.be.revertedWithCustomError(l2MessageService, "MessageDoesNotExistOrHasAlreadyBeenClaimed");
+ });
+
+ it("Should execute the claim message and send fees to recipient, left over fee to destination", async () => {
+ const expectedBytes = await encodeSendMessage(
+ admin.address,
+ notAuthorizedAccount.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ BigNumber.from(1),
+ EMPTY_CALLDATA,
+ );
+
+ const destinationBalance = await notAuthorizedAccount.getBalance();
+ const postmanBalance = await postmanAddress.getBalance();
+
+ await l2MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
+
+ const expectedBytesArray = [ethers.utils.keccak256(expectedBytes)];
+ await l2MessageService.connect(l1l2MessageSetter).addL1L2MessageHashes(expectedBytesArray);
+
+ await l2MessageService.claimMessage(
+ admin.address,
+ notAuthorizedAccount.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ postmanAddress.address,
+ EMPTY_CALLDATA,
+ 1,
+ );
+ // greater due to the gas refund
+ expect(await notAuthorizedAccount.getBalance()).to.be.greaterThan(destinationBalance.add(MESSAGE_VALUE_1ETH));
+ expect(await postmanAddress.getBalance()).to.be.greaterThan(postmanBalance);
+ });
+
+ it("Should execute the claim message and send fees to recipient contract and no leftovers", async () => {
+ const factory = await ethers.getContractFactory("TestReceivingContract");
+ const testContract = (await factory.deploy()) as TestReceivingContract;
+
+ const expectedBytes = await encodeSendMessage(
+ admin.address,
+ testContract.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ BigNumber.from(1),
+ EMPTY_CALLDATA,
+ );
+
+ const postmanBalance = await postmanAddress.getBalance();
+
+ await l2MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
+
+ const expectedBytesArray = [ethers.utils.keccak256(expectedBytes)];
+ await l2MessageService.connect(l1l2MessageSetter).addL1L2MessageHashes(expectedBytesArray);
+
+ await l2MessageService.claimMessage(
+ admin.address,
+ testContract.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ postmanAddress.address,
+ EMPTY_CALLDATA,
+ 1,
+ );
+ // greater due to the gas refund
+ expect(await ethers.provider.getBalance(testContract.address)).to.be.equal(MESSAGE_VALUE_1ETH);
+ expect(await postmanAddress.getBalance()).to.be.equal(postmanBalance.add(MESSAGE_FEE));
+ });
+
+ it("Should execute the claim message and send the fees to set recipient, and NOT refund fee to EOA", async () => {
+ const expectedBytes = await encodeSendMessage(
+ admin.address,
+ notAuthorizedAccount.address,
+ LOW_NO_REFUND_MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ BigNumber.from(1),
+ EMPTY_CALLDATA,
+ );
+
+ const destinationBalance = await notAuthorizedAccount.getBalance();
+ const postmanBalance = await postmanAddress.getBalance();
+
+ await l2MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
+
+ const expectedBytesArray = [ethers.utils.keccak256(expectedBytes)];
+ await l2MessageService.connect(l1l2MessageSetter).addL1L2MessageHashes(expectedBytesArray);
+
+ await l2MessageService.claimMessage(
+ admin.address,
+ notAuthorizedAccount.address,
+ LOW_NO_REFUND_MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ postmanAddress.address,
+ EMPTY_CALLDATA,
+ 1,
+ { gasPrice: 1000000000 },
+ );
+
+ // greater due to the gas refund
+ expect(await notAuthorizedAccount.getBalance()).to.be.equal(destinationBalance.add(MESSAGE_VALUE_1ETH));
+ expect(await postmanAddress.getBalance()).to.be.equal(postmanBalance.add(LOW_NO_REFUND_MESSAGE_FEE));
+ });
+
+ it("Should execute the claim message and send fees to EOA with calldata and no refund sent", async () => {
+ const expectedBytes = await encodeSendMessage(
+ admin.address,
+ notAuthorizedAccount.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ BigNumber.from(1),
+ "0x123456789a",
+ );
+
+ const destinationBalance = await notAuthorizedAccount.getBalance();
+ const postmanBalance = await postmanAddress.getBalance();
+
+ await l2MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
+
+ const expectedBytesArray = [ethers.utils.keccak256(expectedBytes)];
+ await l2MessageService.connect(l1l2MessageSetter).addL1L2MessageHashes(expectedBytesArray);
+
+ await l2MessageService.claimMessage(
+ admin.address,
+ notAuthorizedAccount.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ postmanAddress.address,
+ "0x123456789a",
+ 1,
+ );
+ // greater due to the gas refund
+ expect(await notAuthorizedAccount.getBalance()).to.be.equal(destinationBalance.add(MESSAGE_VALUE_1ETH));
+ expect(await postmanAddress.getBalance()).to.be.equal(postmanBalance.add(MESSAGE_FEE));
+ });
+
+ it("Should execute the claim message and no fees to EOA with calldata and no refund sent", async () => {
+ const expectedBytes = await encodeSendMessage(
+ admin.address,
+ notAuthorizedAccount.address,
+ BigNumber.from(0),
+ MESSAGE_VALUE_1ETH,
+ BigNumber.from(1),
+ "0x123456789a",
+ );
+
+ const destinationBalance = await notAuthorizedAccount.getBalance();
+ const postmanBalance = await postmanAddress.getBalance();
+
+ await l2MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
+
+ const expectedBytesArray = [ethers.utils.keccak256(expectedBytes)];
+ await l2MessageService.connect(l1l2MessageSetter).addL1L2MessageHashes(expectedBytesArray);
+
+ await l2MessageService.claimMessage(
+ admin.address,
+ notAuthorizedAccount.address,
+ BigNumber.from(0),
+ MESSAGE_VALUE_1ETH,
+ postmanAddress.address,
+ "0x123456789a",
+ 1,
+ );
+ // greater due to the gas refund
+ expect(await notAuthorizedAccount.getBalance()).to.be.equal(destinationBalance.add(MESSAGE_VALUE_1ETH));
+ expect(await postmanAddress.getBalance()).to.be.equal(postmanBalance);
+ });
+
+ it("Should execute the claim message and no fees to EOA with no calldata and no refund sent", async () => {
+ const expectedBytes = await encodeSendMessage(
+ admin.address,
+ notAuthorizedAccount.address,
+ BigNumber.from(0),
+ MESSAGE_VALUE_1ETH,
+ BigNumber.from(1),
+ EMPTY_CALLDATA,
+ );
+
+ const destinationBalance = await notAuthorizedAccount.getBalance();
+ const postmanBalance = await postmanAddress.getBalance();
+
+ await l2MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
+
+ const expectedBytesArray = [ethers.utils.keccak256(expectedBytes)];
+ await l2MessageService.connect(l1l2MessageSetter).addL1L2MessageHashes(expectedBytesArray);
+
+ await l2MessageService.claimMessage(
+ admin.address,
+ notAuthorizedAccount.address,
+ BigNumber.from(0),
+ MESSAGE_VALUE_1ETH,
+ postmanAddress.address,
+ EMPTY_CALLDATA,
+ 1,
+ );
+ // greater due to the gas refund
+ expect(await notAuthorizedAccount.getBalance()).to.be.equal(destinationBalance.add(MESSAGE_VALUE_1ETH));
+ expect(await postmanAddress.getBalance()).to.be.equal(postmanBalance);
+ });
+
+ // todo - add tests for refund checks when gas is lower
+
+ it("Should fail to send if the contract is paused", async () => {
+ await l2MessageService.connect(securityCouncil).pauseByType(GENERAL_PAUSE_TYPE);
+
+ await expect(
+ l2MessageService
+ .connect(admin)
+ .canSendMessage(notAuthorizedAccount.address, 0, "0x", { value: INITIAL_WITHDRAW_LIMIT }),
+ )
+ .to.be.revertedWithCustomError(l2MessageService, "IsPaused")
+ .withArgs(GENERAL_PAUSE_TYPE);
+
+ const usedAmount = await l2MessageService.currentPeriodAmountInWei();
+ expect(usedAmount).to.be.equal(0);
+ });
+
+ it("Should fail when the message hash has been claimed", async () => {
+ const expectedBytes = await encodeSendMessage(
+ l2MessageService.address,
+ notAuthorizedAccount.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ BigNumber.from(1),
+ "0x",
+ );
+
+ await l2MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
+
+ const expectedBytesArray = [ethers.utils.keccak256(expectedBytes)];
+ await l2MessageService.connect(l1l2MessageSetter).addL1L2MessageHashes(expectedBytesArray);
+
+ await l2MessageService.claimMessage(
+ l2MessageService.address,
+ notAuthorizedAccount.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ postmanAddress.address,
+ "0x",
+ 1,
+ );
+ await expect(
+ l2MessageService.claimMessage(
+ l2MessageService.address,
+ notAuthorizedAccount.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ postmanAddress.address,
+ "0x",
+ 1,
+ ),
+ ).to.be.revertedWithCustomError(l2MessageService, "MessageDoesNotExistOrHasAlreadyBeenClaimed");
+ });
+
+ it("Should execute the claim message and send the fees to msg.sender", async () => {
+ const expectedBytes = await encodeSendMessage(
+ l2MessageService.address,
+ notAuthorizedAccount.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ BigNumber.from(1),
+ "0x",
+ );
+
+ const expectedSecondBytes = await encodeSendMessage(
+ l2MessageService.address,
+ notAuthorizedAccount.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ BigNumber.from(2),
+ "0x",
+ );
+
+ const destinationBalance = await notAuthorizedAccount.getBalance();
+
+ await l2MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
+
+ const expectedBytesArray = [ethers.utils.keccak256(expectedBytes), ethers.utils.keccak256(expectedSecondBytes)];
+ await l2MessageService.connect(l1l2MessageSetter).addL1L2MessageHashes(expectedBytesArray);
+
+ await l2MessageService
+ .connect(admin)
+ .claimMessage(
+ l2MessageService.address,
+ notAuthorizedAccount.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ ethers.constants.AddressZero,
+ "0x",
+ 1,
+ );
+
+ const adminBalance = await admin.getBalance();
+
+ await l2MessageService
+ .connect(admin)
+ .claimMessage(
+ l2MessageService.address,
+ notAuthorizedAccount.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ ethers.constants.AddressZero,
+ "0x",
+ 2,
+ );
+
+ expect(await notAuthorizedAccount.getBalance()).to.be.greaterThan(
+ destinationBalance.add(MESSAGE_VALUE_1ETH).add(MESSAGE_VALUE_1ETH),
+ );
+ expect(await admin.getBalance()).to.be.greaterThan(adminBalance);
+
+ expect(await l2MessageService.inboxL1L2MessageStatus(ethers.utils.keccak256(expectedBytes))).to.be.equal(
+ INBOX_STATUS_CLAIMED,
+ );
+ });
+
+ // todo also add lower than 5000 gas check for the balances to be equal
+
+ it("Should execute the claim message when there are no fees", async () => {
+ const expectedBytes = await encodeSendMessage(
+ l2MessageService.address,
+ notAuthorizedAccount.address,
+ BigNumber.from(0),
+ MESSAGE_VALUE_1ETH,
+ BigNumber.from(1),
+ "0x",
+ );
+ const destinationBalance = await notAuthorizedAccount.getBalance();
+
+ await l2MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
+
+ const expectedBytesArray = [ethers.utils.keccak256(expectedBytes)];
+ await l2MessageService.connect(l1l2MessageSetter).addL1L2MessageHashes(expectedBytesArray);
+
+ const adminBalance = await admin.getBalance();
+ await l2MessageService
+ .connect(admin)
+ .claimMessage(
+ l2MessageService.address,
+ notAuthorizedAccount.address,
+ 0,
+ MESSAGE_VALUE_1ETH,
+ ethers.constants.AddressZero,
+ "0x",
+ 1,
+ );
+
+ expect(await notAuthorizedAccount.getBalance()).to.be.equal(destinationBalance.add(MESSAGE_VALUE_1ETH));
+ expect(await admin.getBalance()).to.be.lessThan(adminBalance);
+
+ expect(await l2MessageService.inboxL1L2MessageStatus(ethers.utils.keccak256(expectedBytes))).to.be.equal(
+ INBOX_STATUS_CLAIMED,
+ );
+ });
+
+ it("Should provide the correct origin sender", async () => {
+ const sendCalldata = generateKeccak256Hash("setSender()").substring(0, 10);
+
+ const expectedBytes = await encodeSendMessage(
+ l2MessageService.address,
+ l2MessageService.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ BigNumber.from(1),
+ sendCalldata,
+ );
+
+ await l2MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
+
+ const expectedBytesArray = [ethers.utils.keccak256(expectedBytes)];
+ await l2MessageService.connect(l1l2MessageSetter).addL1L2MessageHashes(expectedBytesArray);
+
+ const storedSenderBeforeSending = await l2MessageService.originalSender();
+ expect(storedSenderBeforeSending).to.be.equal(ethers.constants.AddressZero);
+
+ await expect(
+ l2MessageService
+ .connect(admin)
+ .claimMessage(
+ l2MessageService.address,
+ l2MessageService.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ ethers.constants.AddressZero,
+ sendCalldata,
+ 1,
+ ),
+ ).to.not.be.reverted;
+
+ const newSender = await l2MessageService.originalSender();
+ expect(newSender).to.be.equal(l2MessageService.address);
+ });
+
+ it("Should fail on reentry when sending to recipient", async () => {
+ const callSignature = generateKeccak256Hash("doReentry()").substring(0, 10);
+
+ const expectedBytes = await encodeSendMessage(
+ l2MessageService.address,
+ l2MessageService.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ BigNumber.from(1),
+ callSignature,
+ );
+
+ await l2MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
+
+ const expectedBytesArray = [ethers.utils.keccak256(expectedBytes)];
+ await l2MessageService.connect(l1l2MessageSetter).addL1L2MessageHashes(expectedBytesArray);
+
+ await expect(
+ l2MessageService
+ .connect(admin)
+ .claimMessage(
+ l2MessageService.address,
+ l2MessageService.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ ethers.constants.AddressZero,
+ callSignature,
+ 1,
+ ),
+ ).to.be.revertedWith("ReentrancyGuard: reentrant call");
+ });
+
+ it("Should fail when the destination errors", async () => {
+ const expectedBytes = await encodeSendMessage(
+ l2MessageService.address,
+ l2MessageService.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ BigNumber.from(1),
+ "0x",
+ );
+
+ await l2MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
+
+ const expectedBytesArray = [ethers.utils.keccak256(expectedBytes)];
+ await l2MessageService.connect(l1l2MessageSetter).addL1L2MessageHashes(expectedBytesArray);
+
+ await expect(
+ l2MessageService
+ .connect(admin)
+ .claimMessage(
+ l2MessageService.address,
+ l2MessageService.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ ethers.constants.AddressZero,
+ "0x",
+ 1,
+ ),
+ )
+ .to.be.revertedWithCustomError(l2MessageService, "MessageSendingFailed")
+ .withArgs(l2MessageService.address);
+
+ expect(await l2MessageService.inboxL1L2MessageStatus(ethers.utils.keccak256(expectedBytes))).to.be.equal(
+ INBOX_STATUS_RECEIVED,
+ );
+ });
+
+ it("Should fail when the fee recipient fails errors", async () => {
+ const expectedBytes = await encodeSendMessage(
+ l2MessageService.address,
+ admin.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ BigNumber.from(1),
+ "0x",
+ );
+
+ await l2MessageService.addFunds({ value: INITIAL_WITHDRAW_LIMIT });
+
+ const expectedBytesArray = [ethers.utils.keccak256(expectedBytes)];
+ await l2MessageService.connect(l1l2MessageSetter).addL1L2MessageHashes(expectedBytesArray);
+
+ await expect(
+ l2MessageService
+ .connect(admin)
+ .claimMessage(
+ l2MessageService.address,
+ admin.address,
+ MESSAGE_FEE,
+ MESSAGE_VALUE_1ETH,
+ l2MessageService.address,
+ "0x",
+ 1,
+ ),
+ )
+ .to.be.revertedWithCustomError(l2MessageService, "FeePaymentFailed")
+ .withArgs(l2MessageService.address);
+
+ expect(await l2MessageService.inboxL1L2MessageStatus(ethers.utils.keccak256(expectedBytes))).to.be.equal(
+ INBOX_STATUS_RECEIVED,
+ );
+ });
+ });
+ });
+
+ describe("Set minimum fee", () => {
+ it("Should fail when caller is not allowed", async () => {
+ await expect(l2MessageService.connect(notAuthorizedAccount).setMinimumFee(MINIMUM_FEE)).to.be.revertedWith(
+ "AccessControl: account " +
+ notAuthorizedAccount.address.toLowerCase() +
+ " is missing role " +
+ MINIMUM_FEE_SETTER_ROLE,
+ );
+ });
+
+ it("Should set the minimum fee", async () => {
+ await l2MessageService.connect(securityCouncil).setMinimumFee(MINIMUM_FEE);
+
+ expect(await l2MessageService.minimumFeeInWei()).to.be.equal(MINIMUM_FEE);
+ });
+ });
+
+ describe("Pausing contracts", () => {
+ it("Should fail pausing as non-pauser", async () => {
+ expect(await l2MessageService.pauseTypeStatuses(GENERAL_PAUSE_TYPE)).to.be.false;
+
+ await expect(l2MessageService.connect(admin).pauseByType(GENERAL_PAUSE_TYPE)).to.be.revertedWith(
+ "AccessControl: account " + admin.address.toLowerCase() + " is missing role " + PAUSE_MANAGER_ROLE,
+ );
+
+ expect(await l2MessageService.pauseTypeStatuses(GENERAL_PAUSE_TYPE)).to.be.false;
+ });
+
+ it("Should pause as pause manager", async () => {
+ expect(await l2MessageService.pauseTypeStatuses(GENERAL_PAUSE_TYPE)).to.be.false;
+
+ await l2MessageService.connect(securityCouncil).pauseByType(GENERAL_PAUSE_TYPE);
+
+ expect(await l2MessageService.pauseTypeStatuses(GENERAL_PAUSE_TYPE)).to.be.true;
+ });
+ });
+
+ describe("Resetting limits", () => {
+ it("Should reset limits as limitSetter", async () => {
+ let usedAmount = await l2MessageService.currentPeriodAmountInWei();
+ expect(usedAmount).to.be.equal(0);
+
+ await l2MessageService
+ .connect(admin)
+ .sendMessage(notAuthorizedAccount.address, 0, "0x", { value: INITIAL_WITHDRAW_LIMIT });
+
+ usedAmount = await l2MessageService.currentPeriodAmountInWei();
+ expect(usedAmount).to.be.equal(INITIAL_WITHDRAW_LIMIT);
+
+ await l2MessageService.connect(securityCouncil).resetAmountUsedInPeriod();
+ usedAmount = await l2MessageService.currentPeriodAmountInWei();
+ expect(usedAmount).to.be.equal(0);
+ });
+
+ it("Should fail reset limits as non-pauser", async () => {
+ let usedAmount = await l2MessageService.currentPeriodAmountInWei();
+ expect(usedAmount).to.be.equal(0);
+
+ await l2MessageService
+ .connect(admin)
+ .sendMessage(notAuthorizedAccount.address, 0, "0x", { value: INITIAL_WITHDRAW_LIMIT });
+
+ usedAmount = await l2MessageService.currentPeriodAmountInWei();
+ expect(usedAmount).to.be.equal(INITIAL_WITHDRAW_LIMIT);
+
+ await expect(l2MessageService.connect(admin).resetAmountUsedInPeriod()).to.be.revertedWith(
+ "AccessControl: account " + admin.address.toLowerCase() + " is missing role " + RATE_LIMIT_SETTER_ROLE,
+ );
+
+ usedAmount = await l2MessageService.currentPeriodAmountInWei();
+ expect(usedAmount).to.be.equal(INITIAL_WITHDRAW_LIMIT);
+ });
+ });
+});
diff --git a/contracts/test/MessageServiceBase.ts b/contracts/test/MessageServiceBase.ts
new file mode 100644
index 000000000..5fa7da027
--- /dev/null
+++ b/contracts/test/MessageServiceBase.ts
@@ -0,0 +1,97 @@
+import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
+import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
+import { expect } from "chai";
+import { ethers } from "hardhat";
+import { TestL2MessageService, TestMessageServiceBase } from "../typechain-types";
+import { INITIAL_WITHDRAW_LIMIT } from "./utils/constants";
+import { deployUpgradableFromFactory } from "./utils/deployment";
+
+describe("MessageServiceBase", () => {
+ let messageServiceBase: TestMessageServiceBase;
+ let messageService: TestL2MessageService;
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ let admin: SignerWithAddress;
+ let remoteSender: SignerWithAddress;
+ let securityCouncil: SignerWithAddress;
+ let l1L2MessageSetter: SignerWithAddress;
+
+ async function deployMessageServiceBaseFixture() {
+ const messageService = (await deployUpgradableFromFactory("TestL2MessageService", [
+ securityCouncil.address,
+ l1L2MessageSetter.address,
+ 86400,
+ INITIAL_WITHDRAW_LIMIT,
+ ])) as TestL2MessageService;
+
+ const messageServiceBase = (await deployUpgradableFromFactory("TestMessageServiceBase", [
+ messageService.address,
+ remoteSender.address,
+ ])) as TestMessageServiceBase;
+ return { messageService, messageServiceBase };
+ }
+
+ beforeEach(async () => {
+ [admin, remoteSender, securityCouncil, l1L2MessageSetter] = await ethers.getSigners();
+ const contracts = await loadFixture(deployMessageServiceBaseFixture);
+ messageService = contracts.messageService;
+ messageServiceBase = contracts.messageServiceBase;
+ });
+
+ describe("Initialization checks", () => {
+ it("Should revert if message service address is address(0)", async () => {
+ await expect(
+ deployUpgradableFromFactory("TestMessageServiceBase", [ethers.constants.AddressZero, remoteSender.address]),
+ ).to.be.revertedWithCustomError(messageServiceBase, "ZeroAddressNotAllowed");
+ });
+
+ it("It should fail when not initializing", async () => {
+ await expect(messageServiceBase.tryInitialize(messageService.address, remoteSender.address)).to.be.revertedWith(
+ "Initializable: contract is not initializing",
+ );
+ });
+
+ it("Should revert if remote sender address is address(0)", async () => {
+ await expect(
+ deployUpgradableFromFactory("TestMessageServiceBase", [messageService.address, ethers.constants.AddressZero]),
+ ).to.be.revertedWithCustomError(messageServiceBase, "ZeroAddressNotAllowed");
+ });
+
+ it("Should set the value of remoteSender variable in storage", async () => {
+ expect(await messageServiceBase.remoteSender()).to.equal(remoteSender.address);
+ });
+
+ it("Should set the value of messageService variable in storage", async () => {
+ expect(await messageServiceBase.messageService()).to.equal(messageService.address);
+ });
+ });
+
+ describe("onlyMessagingService() modifier", () => {
+ it("Should revert if msg.sender is not the message service address", async () => {
+ await expect(messageServiceBase.withOnlyMessagingService()).to.be.revertedWithCustomError(
+ messageServiceBase,
+ "CallerIsNotMessageService",
+ );
+ });
+
+ it("Should succeed if msg.sender is the message service address", async () => {
+ expect(await messageService.callMessageServiceBase(messageServiceBase.address)).to.not.be.reverted;
+ });
+ });
+
+ describe("onlyAuthorizedRemoteSender() modifier", () => {
+ it("Should revert if sender is not allowed", async () => {
+ await expect(messageServiceBase.withOnlyAuthorizedRemoteSender()).to.be.revertedWithCustomError(
+ messageServiceBase,
+ "SenderNotAuthorized",
+ );
+ });
+
+ it("Should succeed if original sender is allowed", async () => {
+ const messageServiceBase = (await deployUpgradableFromFactory("TestMessageServiceBase", [
+ messageService.address,
+ "0x00000000000000000000000000000000075BCd15",
+ ])) as TestMessageServiceBase;
+ await expect(messageServiceBase.withOnlyAuthorizedRemoteSender()).to.not.be.reverted;
+ });
+ });
+});
diff --git a/contracts/test/PauseManager.ts b/contracts/test/PauseManager.ts
new file mode 100644
index 000000000..851c89de5
--- /dev/null
+++ b/contracts/test/PauseManager.ts
@@ -0,0 +1,197 @@
+import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
+import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
+import { expect } from "chai";
+import { ethers } from "hardhat";
+import { TestPauseManager } from "../typechain-types";
+import {
+ DEFAULT_ADMIN_ROLE,
+ GENERAL_PAUSE_TYPE,
+ L1_L2_PAUSE_TYPE,
+ L2_L1_PAUSE_TYPE,
+ PAUSE_MANAGER_ROLE,
+ PROVING_SYSTEM_PAUSE_TYPE,
+} from "./utils/constants";
+import { deployUpgradableFromFactory } from "./utils/deployment";
+
+async function deployTestPauseManagerFixture(): Promise {
+ return deployUpgradableFromFactory("TestPauseManager") as Promise;
+}
+
+describe("PauseManager", () => {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ let defaultAdmin: SignerWithAddress;
+ let pauseManagerAccount: SignerWithAddress;
+ let nonManager: SignerWithAddress;
+ let pauseManager: TestPauseManager;
+
+ beforeEach(async () => {
+ [defaultAdmin, pauseManagerAccount, nonManager] = await ethers.getSigners();
+ pauseManager = await loadFixture(deployTestPauseManagerFixture);
+
+ await pauseManager.grantRole(PAUSE_MANAGER_ROLE, pauseManagerAccount.address);
+ });
+
+ describe("Initialization checks", () => {
+ it("Deployer has default admin role", async () => {
+ expect(await pauseManager.hasRole(DEFAULT_ADMIN_ROLE, defaultAdmin.address)).to.be.true;
+ });
+
+ it("Second initialisation while initializing fails", async () => {
+ await expect(pauseManager.initialize()).to.be.revertedWith("Initializable: contract is already initialized");
+ });
+ });
+
+ describe("General pausing", () => {
+ // can pause as PAUSE_MANAGER_ROLE
+ it("should pause the contract if PAUSE_MANAGER_ROLE", async () => {
+ await pauseManager.connect(pauseManagerAccount).pauseByType(GENERAL_PAUSE_TYPE);
+ expect(await pauseManager.pauseTypeStatuses(GENERAL_PAUSE_TYPE)).to.be.true;
+ });
+
+ // cannot pause as non-PAUSE_MANAGER_ROLE
+ it("should revert pause attempt if not PAUSE_MANAGER_ROLE", async () => {
+ await expect(pauseManager.connect(nonManager).pauseByType(GENERAL_PAUSE_TYPE)).to.be.revertedWith(
+ "AccessControl: account " + nonManager.address.toLowerCase() + " is missing role " + PAUSE_MANAGER_ROLE,
+ );
+ });
+
+ // can unpause as PAUSE_MANAGER_ROLE
+ it("should unpause the contract if PAUSE_MANAGER_ROLE", async () => {
+ await pauseManager.connect(pauseManagerAccount).pauseByType(GENERAL_PAUSE_TYPE);
+ await pauseManager.connect(pauseManagerAccount).unPauseByType(GENERAL_PAUSE_TYPE);
+ expect(await pauseManager.pauseTypeStatuses(GENERAL_PAUSE_TYPE)).to.be.false;
+ });
+
+ // cannot unpause as non-PAUSE_MANAGER_ROLE
+ it("should revert unpause attempt if not PAUSE_MANAGER_ROLE", async () => {
+ await pauseManager.connect(pauseManagerAccount).pauseByType(GENERAL_PAUSE_TYPE);
+ await expect(pauseManager.connect(nonManager).unPauseByType(GENERAL_PAUSE_TYPE)).to.be.revertedWith(
+ "AccessControl: account " + nonManager.address.toLowerCase() + " is missing role " + PAUSE_MANAGER_ROLE,
+ );
+ });
+ });
+
+ describe("Pause and unpause event emitting", () => {
+ it("should pause the L1_L2_PAUSE_TYPE", async () => {
+ await expect(pauseManager.connect(pauseManagerAccount).pauseByType(L1_L2_PAUSE_TYPE))
+ .to.emit(pauseManager, "Paused")
+ .withArgs(pauseManagerAccount.address, L1_L2_PAUSE_TYPE);
+ });
+
+ it("should unpause the L1_L2_PAUSE_TYPE", async () => {
+ await pauseManager.connect(pauseManagerAccount).pauseByType(L1_L2_PAUSE_TYPE);
+ await expect(pauseManager.connect(pauseManagerAccount).unPauseByType(L1_L2_PAUSE_TYPE))
+ .to.emit(pauseManager, "UnPaused")
+ .withArgs(pauseManagerAccount.address, L1_L2_PAUSE_TYPE);
+ });
+ });
+
+ describe("Specific type pausing", () => {
+ describe("With permissions as PAUSE_MANAGER_ROLE", () => {
+ it("should pause the L1_L2_PAUSE_TYPE", async () => {
+ await pauseManager.connect(pauseManagerAccount).pauseByType(L1_L2_PAUSE_TYPE);
+ expect(await pauseManager.pauseTypeStatuses(L1_L2_PAUSE_TYPE)).to.be.true;
+ });
+
+ it("should unpause the L1_L2_PAUSE_TYPE", async () => {
+ await pauseManager.connect(pauseManagerAccount).pauseByType(L1_L2_PAUSE_TYPE);
+ await pauseManager.connect(pauseManagerAccount).unPauseByType(L1_L2_PAUSE_TYPE);
+ expect(await pauseManager.pauseTypeStatuses(L1_L2_PAUSE_TYPE)).to.be.false;
+ });
+
+ it("should pause the L2_L1_PAUSE_TYPE", async () => {
+ await pauseManager.connect(pauseManagerAccount).pauseByType(L2_L1_PAUSE_TYPE);
+ expect(await pauseManager.pauseTypeStatuses(L2_L1_PAUSE_TYPE)).to.be.true;
+ });
+
+ it("should unpause the L2_L1_PAUSE_TYPE", async () => {
+ await pauseManager.connect(pauseManagerAccount).pauseByType(L2_L1_PAUSE_TYPE);
+ await pauseManager.connect(pauseManagerAccount).unPauseByType(L2_L1_PAUSE_TYPE);
+ expect(await pauseManager.pauseTypeStatuses(L2_L1_PAUSE_TYPE)).to.be.false;
+ });
+
+ it("should pause the PROVING_SYSTEM_PAUSE_TYPE", async () => {
+ await pauseManager.connect(pauseManagerAccount).pauseByType(PROVING_SYSTEM_PAUSE_TYPE);
+ expect(await pauseManager.pauseTypeStatuses(PROVING_SYSTEM_PAUSE_TYPE)).to.be.true;
+ });
+
+ it("should unpause the PROVING_SYSTEM_PAUSE_TYPE", async () => {
+ await pauseManager.connect(pauseManagerAccount).pauseByType(PROVING_SYSTEM_PAUSE_TYPE);
+ await pauseManager.connect(pauseManagerAccount).unPauseByType(PROVING_SYSTEM_PAUSE_TYPE);
+ expect(await pauseManager.pauseTypeStatuses(PROVING_SYSTEM_PAUSE_TYPE)).to.be.false;
+ });
+ });
+
+ describe("Without permissions - non-PAUSE_MANAGER_ROLE", () => {
+ it("cannot pause the L1_L2_PAUSE_TYPE as non-manager", async () => {
+ await expect(pauseManager.connect(nonManager).pauseByType(L1_L2_PAUSE_TYPE)).to.be.revertedWith(
+ "AccessControl: account " + nonManager.address.toLowerCase() + " is missing role " + PAUSE_MANAGER_ROLE,
+ );
+ });
+
+ it("cannot unpause the L2_L1_PAUSE_TYPE", async () => {
+ await pauseManager.connect(pauseManagerAccount).pauseByType(L1_L2_PAUSE_TYPE);
+
+ await expect(pauseManager.connect(nonManager).unPauseByType(L1_L2_PAUSE_TYPE)).to.be.revertedWith(
+ "AccessControl: account " + nonManager.address.toLowerCase() + " is missing role " + PAUSE_MANAGER_ROLE,
+ );
+ });
+
+ it("cannot pause the L2_L1_PAUSE_TYPE as non-manager", async () => {
+ await expect(pauseManager.connect(nonManager).pauseByType(L2_L1_PAUSE_TYPE)).to.be.revertedWith(
+ "AccessControl: account " + nonManager.address.toLowerCase() + " is missing role " + PAUSE_MANAGER_ROLE,
+ );
+ });
+
+ it("cannot unpause the L2_L1_PAUSE_TYPE", async () => {
+ await pauseManager.connect(pauseManagerAccount).pauseByType(L2_L1_PAUSE_TYPE);
+
+ await expect(pauseManager.connect(nonManager).unPauseByType(L2_L1_PAUSE_TYPE)).to.be.revertedWith(
+ "AccessControl: account " + nonManager.address.toLowerCase() + " is missing role " + PAUSE_MANAGER_ROLE,
+ );
+ });
+
+ it("cannot pause the PROVING_SYSTEM_PAUSE_TYPE as non-manager", async () => {
+ await expect(pauseManager.connect(nonManager).pauseByType(PROVING_SYSTEM_PAUSE_TYPE)).to.be.revertedWith(
+ "AccessControl: account " + nonManager.address.toLowerCase() + " is missing role " + PAUSE_MANAGER_ROLE,
+ );
+ });
+
+ it("cannot unpause the PROVING_SYSTEM_PAUSE_TYPE", async () => {
+ await pauseManager.connect(pauseManagerAccount).pauseByType(PROVING_SYSTEM_PAUSE_TYPE);
+
+ await expect(pauseManager.connect(nonManager).unPauseByType(PROVING_SYSTEM_PAUSE_TYPE)).to.be.revertedWith(
+ "AccessControl: account " + nonManager.address.toLowerCase() + " is missing role " + PAUSE_MANAGER_ROLE,
+ );
+ });
+ });
+
+ describe("Incorrect states for pausing and unpausing", () => {
+ it("Should pause and fail to pause when paused", async () => {
+ await pauseManager.connect(pauseManagerAccount).pauseByType(L1_L2_PAUSE_TYPE);
+
+ await expect(
+ pauseManager.connect(pauseManagerAccount).pauseByType(L1_L2_PAUSE_TYPE),
+ ).to.be.revertedWithCustomError(pauseManager, "IsPaused");
+ });
+
+ it("Should allow other types to pause if one is paused", async () => {
+ await pauseManager.connect(pauseManagerAccount).pauseByType(L1_L2_PAUSE_TYPE);
+
+ await expect(
+ pauseManager.connect(pauseManagerAccount).pauseByType(L1_L2_PAUSE_TYPE),
+ ).to.be.revertedWithCustomError(pauseManager, "IsPaused");
+
+ await expect(pauseManager.connect(pauseManagerAccount).pauseByType(PROVING_SYSTEM_PAUSE_TYPE))
+ .to.emit(pauseManager, "Paused")
+ .withArgs(pauseManagerAccount.address, PROVING_SYSTEM_PAUSE_TYPE);
+ });
+
+ it("Should fail to unpause if not paused", async () => {
+ await expect(
+ pauseManager.connect(pauseManagerAccount).unPauseByType(L1_L2_PAUSE_TYPE),
+ ).to.be.revertedWithCustomError(pauseManager, "IsNotPaused");
+ });
+ });
+ });
+});
diff --git a/contracts/test/RateLimiter.ts b/contracts/test/RateLimiter.ts
new file mode 100644
index 000000000..1ce3568b5
--- /dev/null
+++ b/contracts/test/RateLimiter.ts
@@ -0,0 +1,162 @@
+import { loadFixture, time } from "@nomicfoundation/hardhat-network-helpers";
+import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
+import { expect } from "chai";
+import { ethers } from "hardhat";
+import { TestRateLimiter } from "../typechain-types";
+import { DEFAULT_ADMIN_ROLE, ONE_DAY_IN_SECONDS, RATE_LIMIT_SETTER_ROLE } from "./utils/constants";
+import { deployUpgradableFromFactory } from "./utils/deployment";
+
+describe("Rate limiter", () => {
+ let testRateLimiter: TestRateLimiter;
+ let defaultAdmin: SignerWithAddress;
+ let resetAdmin: SignerWithAddress;
+
+ const ONE_HUNDRED_ETH = ethers.utils.parseUnits("100", 18);
+ const NINE_HUNDRED_ETH = ethers.utils.parseUnits("900", 18);
+
+ async function deployTestRateLimiterFixture(): Promise {
+ return deployUpgradableFromFactory("TestRateLimiter", [86400, ethers.utils.parseEther("1000")], {
+ initializer: "initialize(uint256,uint256)",
+ }) as Promise;
+ }
+
+ before(async () => {
+ [defaultAdmin, resetAdmin] = await ethers.getSigners();
+ });
+
+ beforeEach(async () => {
+ testRateLimiter = await loadFixture(deployTestRateLimiterFixture);
+ await testRateLimiter.grantRole(RATE_LIMIT_SETTER_ROLE, resetAdmin.address);
+ });
+
+ describe("Initialization checks", () => {
+ it("Deployer has default admin role", async () => {
+ expect(await testRateLimiter.hasRole(DEFAULT_ADMIN_ROLE, defaultAdmin.address)).to.be.true;
+ });
+
+ it("fails to initialise when not initialising", async () => {
+ await expect(testRateLimiter.tryInitialize(86400, ONE_HUNDRED_ETH)).to.be.revertedWith(
+ "Initializable: contract is not initializing",
+ );
+ });
+
+ it("fails to initialise when the limit is zero", async () => {
+ await expect(
+ deployUpgradableFromFactory("TestRateLimiter", [86400, 0], { initializer: "initialize(uint256,uint256)" }),
+ ).to.revertedWithCustomError(testRateLimiter, "LimitIsZero");
+ });
+
+ it("fails to initialise when the period is zero", async () => {
+ await expect(
+ deployUpgradableFromFactory("TestRateLimiter", [0, ethers.utils.parseEther("1000")], {
+ initializer: "initialize(uint256,uint256)",
+ }),
+ ).to.revertedWithCustomError(testRateLimiter, "PeriodIsZero");
+ });
+ });
+
+ describe("Rate limit values", () => {
+ it("currentPeriodAmountInWei increases when amounts withdrawn", async () => {
+ expect(await testRateLimiter.currentPeriodAmountInWei()).to.be.equal(0);
+
+ await testRateLimiter.withdrawSomeAmount(ONE_HUNDRED_ETH);
+ expect(await testRateLimiter.currentPeriodAmountInWei()).to.be.equal(ONE_HUNDRED_ETH);
+ });
+
+ it("currentPeriodAmountInWei increases to the limit", async () => {
+ await testRateLimiter.withdrawSomeAmount(ONE_HUNDRED_ETH);
+ await testRateLimiter.withdrawSomeAmount(NINE_HUNDRED_ETH);
+ expect(await testRateLimiter.currentPeriodAmountInWei()).to.be.equal(ONE_HUNDRED_ETH.add(NINE_HUNDRED_ETH));
+ });
+
+ it("withdrawing beyond the limit fails", async () => {
+ await testRateLimiter.withdrawSomeAmount(ONE_HUNDRED_ETH.add(NINE_HUNDRED_ETH));
+ expect(await testRateLimiter.currentPeriodAmountInWei()).to.be.equal(ONE_HUNDRED_ETH.add(NINE_HUNDRED_ETH));
+ await expect(testRateLimiter.withdrawSomeAmount(1)).to.revertedWithCustomError(
+ testRateLimiter,
+ "RateLimitExceeded",
+ );
+ });
+
+ it("limit resets with time", async () => {
+ await time.increase(ONE_DAY_IN_SECONDS);
+ await testRateLimiter.withdrawSomeAmount(ONE_HUNDRED_ETH);
+ expect(await testRateLimiter.currentPeriodAmountInWei()).to.be.equal(ONE_HUNDRED_ETH);
+ });
+
+ it("limit resets with time", async () => {
+ await time.increase(ONE_DAY_IN_SECONDS); //one day in seconds
+ await testRateLimiter.withdrawSomeAmount(ONE_HUNDRED_ETH);
+ expect(await testRateLimiter.currentPeriodAmountInWei())
+ .to.emit(testRateLimiter, "AmountUsedInPeriodReset")
+ .withArgs(resetAdmin.address);
+ });
+ });
+
+ describe("Limit amount resetting", () => {
+ it("resetting currentPeriodAmountInWei fails if not admin", async () => {
+ await testRateLimiter.withdrawSomeAmount(ONE_HUNDRED_ETH);
+ expect(await testRateLimiter.currentPeriodAmountInWei()).to.be.equal(ONE_HUNDRED_ETH);
+
+ await expect(testRateLimiter.resetAmountUsedInPeriod()).to.be.revertedWith(
+ "AccessControl: account " + defaultAdmin.address.toLowerCase() + " is missing role " + RATE_LIMIT_SETTER_ROLE,
+ );
+ expect(await testRateLimiter.currentPeriodAmountInWei()).to.be.equal(ONE_HUNDRED_ETH);
+ });
+
+ it("resetting currentPeriodAmountInWei succeeds", async () => {
+ expect(await testRateLimiter.currentPeriodAmountInWei()).to.be.equal(0);
+
+ await testRateLimiter.withdrawSomeAmount(ONE_HUNDRED_ETH);
+ expect(await testRateLimiter.currentPeriodAmountInWei()).to.be.equal(ONE_HUNDRED_ETH);
+
+ await testRateLimiter.connect(resetAdmin).resetAmountUsedInPeriod();
+ expect(await testRateLimiter.currentPeriodAmountInWei()).to.be.equal(0);
+ });
+
+ it("resetting limit amount fails if not admin", async () => {
+ await testRateLimiter.withdrawSomeAmount(ONE_HUNDRED_ETH);
+ expect(await testRateLimiter.currentPeriodAmountInWei()).to.be.equal(ONE_HUNDRED_ETH);
+
+ await expect(testRateLimiter.resetRateLimitAmount(NINE_HUNDRED_ETH)).to.be.revertedWith(
+ "AccessControl: account " + defaultAdmin.address.toLowerCase() + " is missing role " + RATE_LIMIT_SETTER_ROLE,
+ );
+ expect(await testRateLimiter.currentPeriodAmountInWei()).to.be.equal(ONE_HUNDRED_ETH);
+ });
+
+ it("resetting limit amount succeeds resetting amount used", async () => {
+ await testRateLimiter.withdrawSomeAmount(NINE_HUNDRED_ETH);
+
+ await expect(testRateLimiter.connect(resetAdmin).resetRateLimitAmount(ONE_HUNDRED_ETH))
+ .to.emit(testRateLimiter, "LimitAmountChanged")
+ .withArgs(resetAdmin.address, ONE_HUNDRED_ETH, true, false);
+
+ expect(await testRateLimiter.currentPeriodAmountInWei()).to.be.equal(ONE_HUNDRED_ETH);
+ });
+
+ it("resetting limit amount succeeds without resetting amount used", async () => {
+ await testRateLimiter.withdrawSomeAmount(ONE_HUNDRED_ETH);
+
+ await expect(testRateLimiter.connect(resetAdmin).resetRateLimitAmount(ONE_HUNDRED_ETH))
+ .to.emit(testRateLimiter, "LimitAmountChanged")
+ .withArgs(resetAdmin.address, ONE_HUNDRED_ETH, false, false);
+
+ expect(await testRateLimiter.currentPeriodAmountInWei()).to.be.equal(ONE_HUNDRED_ETH);
+ });
+
+ it("Resetting limits with period expired fires the correct event", async () => {
+ await time.increase(ONE_DAY_IN_SECONDS);
+ await expect(testRateLimiter.connect(resetAdmin).resetRateLimitAmount(ONE_HUNDRED_ETH))
+ .to.emit(testRateLimiter, "LimitAmountChanged")
+ .withArgs(resetAdmin.address, ONE_HUNDRED_ETH, false, true);
+ });
+
+ it("Resetting limits with period sets the used amount to zero", async () => {
+ await testRateLimiter.withdrawSomeAmount(ONE_HUNDRED_ETH);
+ expect(await testRateLimiter.currentPeriodAmountInWei()).to.be.equal(ONE_HUNDRED_ETH);
+ await time.increase(ONE_DAY_IN_SECONDS);
+ await testRateLimiter.connect(resetAdmin).resetRateLimitAmount(ONE_HUNDRED_ETH);
+ expect(await testRateLimiter.currentPeriodAmountInWei()).to.be.equal(0);
+ });
+ });
+});
diff --git a/contracts/test/Rlp.ts b/contracts/test/Rlp.ts
new file mode 100644
index 000000000..932b87cd1
--- /dev/null
+++ b/contracts/test/Rlp.ts
@@ -0,0 +1,211 @@
+import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
+import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
+import { expect } from "chai";
+import { ethers } from "hardhat";
+import { TestRlp } from "../typechain-types";
+import { deployFromFactory } from "./utils/deployment";
+import { getRLPEncodeTransactions } from "./utils/helpers";
+
+describe("Rlp", () => {
+ let contract: TestRlp;
+ let account: SignerWithAddress;
+
+ async function deployRlpFixture() {
+ return deployFromFactory("TestRlp") as Promise;
+ }
+
+ const { eip1559Transaction, shortEip1559Transaction } = getRLPEncodeTransactions("test-transactions.json");
+
+ before(async () => {
+ [account] = await ethers.getSigners();
+ });
+
+ beforeEach(async () => {
+ contract = await loadFixture(deployRlpFixture);
+ });
+
+ describe("Transactions are wrong length", () => {
+ it("Should revert when decoding is too short", async () => {
+ // note the 10 vs. 9 - the encoding adds a C0 at the end so the 9th element is the empty access list
+ await expect(contract.skipTo(ethers.utils.hexDataSlice(shortEip1559Transaction, 1), 9)).to.be.reverted;
+ });
+ });
+
+ describe("isList", () => {
+ it("Should return true if bytes is a list", async () => {
+ const list = new Uint8Array([0x12, 0x34, 0x56, 0x78]);
+ const encodedList = ethers.utils.RLP.encode([list]);
+ expect(await contract.isList(encodedList)).to.be.true;
+ });
+
+ it("Should return false if bytes is a not list", async () => {
+ const encodedString = ethers.utils.RLP.encode("0x12345678");
+ expect(await contract.isList(encodedString)).to.be.false;
+ });
+
+ it("Should return false if bytes length === 0", async () => {
+ expect(await contract.isList("0x")).to.be.false;
+ });
+ });
+
+ describe("itemLength", () => {
+ it("Should return the byte length for a single byte", async () => {
+ const str = "a";
+ const encodedList = ethers.utils.RLP.encode(ethers.utils.hexlify(ethers.utils.toUtf8Bytes(str)));
+ const itemLength = await contract.itemLength(encodedList);
+ expect(itemLength.toNumber()).to.equal(ethers.utils.hexDataLength(encodedList));
+ });
+
+ it("Should return the byte length for a 0-55 bytes length", async () => {
+ const str = "dog";
+ const encodedList = ethers.utils.RLP.encode(ethers.utils.hexlify(ethers.utils.toUtf8Bytes(str)));
+ const itemLength = await contract.itemLength(encodedList);
+ expect(itemLength.toNumber()).to.equal(ethers.utils.hexDataLength(encodedList));
+ });
+
+ it("Should return the byte length for a >55 bytes length", async () => {
+ const str = "zoo255zoo255zzzzzzzzzzzzssssssssssssssssssssssssssssssssssssssssssssss";
+ const encodedList = ethers.utils.RLP.encode(ethers.utils.hexlify(ethers.utils.toUtf8Bytes(str)));
+ const itemLength = await contract.itemLength(encodedList);
+ expect(itemLength.toNumber()).to.equal(ethers.utils.hexDataLength(encodedList));
+ });
+
+ it("Should return the byte length for a list with length 0-55", async () => {
+ const list = [
+ ethers.utils.hexlify(ethers.utils.toUtf8Bytes("dog")),
+ ethers.utils.hexlify(ethers.utils.toUtf8Bytes("god")),
+ ethers.utils.hexlify(ethers.utils.toUtf8Bytes("cat")),
+ ];
+ const encodedList = ethers.utils.RLP.encode(list);
+ const itemLength = await contract.itemLength(encodedList);
+ expect(itemLength.toNumber()).to.equal(ethers.utils.hexDataLength(encodedList));
+ });
+
+ it("Should return the byte length for a list with length >55", async () => {
+ const list = [
+ ethers.utils.hexlify(ethers.utils.toUtf8Bytes("dog")),
+ ethers.utils.hexlify(ethers.utils.toUtf8Bytes("god")),
+ ethers.utils.hexlify(ethers.utils.toUtf8Bytes("cat")),
+ ethers.utils.hexlify(
+ ethers.utils.toUtf8Bytes("255255zzzzzzzzzzzzaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
+ ),
+ ];
+ const encodedList = ethers.utils.RLP.encode(list);
+ const itemLength = await contract.itemLength(encodedList);
+ expect(itemLength.toNumber()).to.equal(ethers.utils.hexDataLength(encodedList));
+ });
+ });
+
+ describe("next", () => {
+ it("Should revert if there is no next item", async () => {
+ const encodedList = ethers.utils.RLP.encode([]);
+ await expect(contract.next(encodedList)).to.be.revertedWithCustomError(contract, "NoNext");
+ });
+
+ it("Should return the next RLP item (for eip1559, the nextPtr should be the chain ID)", async () => {
+ const [nextItem, itemNextMemPtr] = await contract.next(
+ ethers.utils.hexDataSlice(
+ "0x02f887800b4d8202918301a48a94000000000000000000000000000000000000000080b864f4b476e100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001497569c1f1e97fb9eca5b5f7c7153f2eb6f9edd312bd4d0dc529f15ae67a7cbec0",
+ 1,
+ ),
+ );
+ expect(nextItem.len).to.equal(1);
+ expect(nextItem.memPtr.toNumber() - itemNextMemPtr.toNumber()).to.equal(0);
+ });
+ });
+
+ describe("hasNext", () => {
+ it("Should return true if the RLP item has a nextPtr", async () => {
+ const hasNext = await contract.hasNext(ethers.utils.hexDataSlice(eip1559Transaction, 1));
+ expect(hasNext).to.equal(true);
+ });
+ });
+
+ describe("skipTo", () => {
+ it("Should return the destination address in the iteration (for an eip1559 encoded transaction)", async () => {
+ const item = await contract.skipTo(ethers.utils.hexDataSlice(eip1559Transaction, 1), 6);
+ const destinationAddress = await contract.fromRlpItemToAddress({ memPtr: item.memPtr, len: item.len });
+ expect(destinationAddress).to.equal("0x0000000000000000000000000000000000aD0000");
+ });
+
+ it("Should revert if it goes beyond the number of elements)", async () => {
+ await expect(contract.skipTo(ethers.utils.hexDataSlice(eip1559Transaction, 1), 9)).to.be.revertedWithCustomError(
+ contract,
+ "MemoryOutOfBounds",
+ );
+ });
+
+ it("Should not revert if it meets maximum elements)", async () => {
+ await expect(
+ contract.skipTo(ethers.utils.hexDataSlice(eip1559Transaction, 1), 8),
+ ).to.not.be.revertedWithCustomError(contract, "MemoryOutOfBounds");
+ });
+ });
+
+ describe("iterator", () => {
+ it("Should revert if the RLP item is not a list", async () => {
+ const encodedAddress = ethers.utils.RLP.encode(account.address);
+ await expect(contract.iterator(encodedAddress)).to.be.revertedWithCustomError(contract, "NotList");
+ });
+
+ it("Should succeed if the RLP item is a long list", async () => {
+ expect(await contract.iterator(ethers.utils.hexDataSlice(eip1559Transaction, 1))).to.not.be.reverted;
+ });
+ });
+
+ describe("payloadLocation", () => {
+ it("Should return the correct payload location", async () => {
+ const payload = await contract.payloadLocation(ethers.utils.hexDataSlice(eip1559Transaction, 1));
+ expect(payload.itemlen.toNumber()).to.equal(648);
+ expect(payload.ptr.toNumber() - payload.rlpItemPtr.toNumber()).to.equal(3);
+ });
+ });
+
+ describe("toAddress", () => {
+ it("Should revert if rlp encoded bytes length !== 21", async () => {
+ const encodedAddress = ethers.utils.RLP.encode(`${account.address}01`);
+ await expect(contract.toAddress(encodedAddress)).to.be.revertedWithCustomError(contract, "WrongBytesLength");
+ });
+
+ it("Should convert rlp encoded bytes to address", async () => {
+ const encodedAddress = ethers.utils.RLP.encode(account.address);
+ const address = await contract.toAddress(encodedAddress);
+ expect(address).to.equal(account.address);
+ });
+ });
+
+ describe("toBytes", () => {
+ it("Should revert if rlp encoded bytes length == 0", async () => {
+ await expect(contract.toBytes("0x")).to.be.revertedWithCustomError(contract, "WrongBytesLength");
+ });
+
+ it("Should convert rlp encoded bytes to bytes", async () => {
+ const encodedBytes = ethers.utils.RLP.encode("0x12345678");
+ const bytes = await contract.toBytes(encodedBytes);
+ expect(bytes).to.equal("0x12345678");
+ });
+ });
+
+ describe("toUint", () => {
+ it("Should revert if rlp encoded bytes length == 0", async () => {
+ await expect(contract.toUint("0x")).to.be.revertedWithCustomError(contract, "WrongBytesLength");
+ });
+
+ it("Should revert if rlp encoded bytes length > 33", async () => {
+ await expect(
+ contract.toUint(
+ ethers.utils.hexDataSlice(
+ "0x02f887800b4d8202918301a48a94000000000000000000000000000000000000000080b864f4b476e100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001497569c1f1e97fb9eca5b5f7c7153f2eb6f9edd312bd4d0dc529f15ae67a7cbec0",
+ 1,
+ ),
+ ),
+ ).to.be.revertedWithCustomError(contract, "WrongBytesLength");
+ });
+
+ it("Should convert rlp encoded bytes to uint", async () => {
+ const encodedBytes = ethers.utils.RLP.encode("0x28");
+ const uint = await contract.toUint(encodedBytes);
+ expect(uint).to.equal(40);
+ });
+ });
+});
diff --git a/contracts/test/Timelock.ts b/contracts/test/Timelock.ts
new file mode 100644
index 000000000..9f335ac25
--- /dev/null
+++ b/contracts/test/Timelock.ts
@@ -0,0 +1,53 @@
+import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
+import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
+import { expect } from "chai";
+import { ethers } from "hardhat";
+import { TimeLock } from "../typechain-types";
+import { CANCELLER_ROLE, EXECUTOR_ROLE, PROPOSER_ROLE, TIMELOCK_ADMIN_ROLE } from "./utils/constants";
+import { deployFromFactory } from "./utils/deployment";
+
+describe("Timelock", () => {
+ let contract: TimeLock;
+ let proposer: SignerWithAddress;
+ let executor: SignerWithAddress;
+
+ async function deployTimeLockFixture() {
+ return deployFromFactory(
+ "TimeLock",
+ 10,
+ [proposer.address],
+ [executor.address],
+ ethers.constants.AddressZero,
+ ) as Promise;
+ }
+
+ before(async () => {
+ [, proposer, executor] = await ethers.getSigners();
+ });
+
+ beforeEach(async () => {
+ contract = await loadFixture(deployTimeLockFixture);
+ });
+
+ describe("Initialization", () => {
+ it("Timelock contract should have the 'TIMELOCK_ADMIN_ROLE' role", async () => {
+ expect(await contract.hasRole(TIMELOCK_ADMIN_ROLE, contract.address)).to.be.true;
+ });
+
+ it("Proposer address should have the 'PROPOSER_ROLE' role", async () => {
+ expect(await contract.hasRole(PROPOSER_ROLE, proposer.address)).to.be.true;
+ });
+
+ it("Proposer address should have the 'CANCELLER_ROLE' role", async () => {
+ expect(await contract.hasRole(CANCELLER_ROLE, proposer.address)).to.be.true;
+ });
+
+ it("Executor address should have the 'EXECUTOR_ROLE' role", async () => {
+ expect(await contract.hasRole(EXECUTOR_ROLE, executor.address)).to.be.true;
+ });
+
+ it("Should set the minDelay state variable with the value passed in the contructor params", async () => {
+ expect(await contract.getMinDelay()).to.equal(10);
+ });
+ });
+});
diff --git a/contracts/test/TransactionDecoder.ts b/contracts/test/TransactionDecoder.ts
new file mode 100644
index 000000000..594dfd2cc
--- /dev/null
+++ b/contracts/test/TransactionDecoder.ts
@@ -0,0 +1,52 @@
+import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
+import { expect } from "chai";
+import { ethers } from "hardhat";
+import { TestTransactionDecoder } from "../typechain-types";
+import { getRLPEncodeTransactions } from "./utils/helpers";
+
+describe("TransactionDecoder", () => {
+ let contract: TestTransactionDecoder;
+ const {
+ eip1559Transaction,
+ eip1559TransactionHashes,
+ legacyTransaction,
+ legacyTransactionHashes,
+ eip2930Transaction,
+ eip2930TransactionHashes,
+ } = getRLPEncodeTransactions("test-transactions.json");
+
+ async function deployTestTransactionDecoderFixture() {
+ const factory = await ethers.getContractFactory("TestTransactionDecoder");
+ contract = await factory.deploy();
+ return contract;
+ }
+
+ beforeEach(async () => {
+ contract = await loadFixture(deployTestTransactionDecoderFixture);
+ });
+
+ describe("Decoding transactions", () => {
+ it("Should decode the hashes from an EIP1559 transaction", async () => {
+ const hashes = await contract.decodeTransactionAndHashes(eip1559Transaction);
+ expect(hashes).to.deep.equal(eip1559TransactionHashes);
+ });
+
+ it("Should decode the hashes from the legacy transaction", async () => {
+ const hashes = await contract.decodeTransactionAndHashes(legacyTransaction);
+ expect(hashes).to.deep.equal(legacyTransactionHashes);
+ });
+
+ it("Should decode the hashes from and EIP2930 transaction", async () => {
+ const hashes = await contract.decodeTransactionAndHashes(eip2930Transaction);
+ expect(hashes).to.deep.equal(eip2930TransactionHashes);
+ });
+
+ it("Should revert with too short data", async () => {
+ await expect(contract.decodeTransactionAndHashes("0x")).to.be.reverted;
+ });
+
+ it("Should revert with unknown tx type", async () => {
+ await expect(contract.decodeTransactionAndHashes("0x03")).to.be.reverted;
+ });
+ });
+});
diff --git a/contracts/test/V2 Scenarios/MessageManager.md b/contracts/test/V2 Scenarios/MessageManager.md
new file mode 100644
index 000000000..165780b44
--- /dev/null
+++ b/contracts/test/V2 Scenarios/MessageManager.md
@@ -0,0 +1,38 @@
+ # Message Manager Test Scenarios
+
+
+## Roles
+- `ORIGIN_BATCH_SETTER` coordinator setting the origin block hashes
+- `MESSAGE_SERVICE` for outbox messages
+
+## Tests
+### Initialize tests
+ - Empty address check for `ORIGIN_BATCH_SETTER`
+ - Empty address check for `MESSAGE_SERVICE`
+ - Can't initialise a second time
+ - `pauserRoleAddress` not `zero address`
+ - `pauserRoleAddress` is set
+
+### Get claimedMessages message status (public mapping)
+- can get message status for claimed hash
+- returns empty value for non-existing hash
+
+### Add origin block and message hashes
+ - reverts `if paused`
+
+ **If not paused:**
+ - can add non-existing batch of hashes as `ORIGIN_BATCH_SETTER`
+ - can't add batch of hashes where an existing duplicate exists as `ORIGIN_BATCH_SETTER` - reverts
+ - can't add batch of hashes as non-`ORIGIN_BATCH_SETTER` - reverts
+ - Any empty reference (sent data) reverts the batch
+ - Empty list of hashes reverts
+ - Block number 0 reverts
+
+### Add Single Message Reference set tests for outbox
+ - reverts `if paused`
+
+ **If not paused:**
+ - can add non-existing as `MESSAGE_SERVICE`
+ - can't add duplicate existing as `MESSAGE_SERVICE`
+ - can't add duplicate existing as non-`MESSAGE_SERVICE`
+ - reference (sent data) cannot be empty - reverts
\ No newline at end of file
diff --git a/contracts/test/V2 Scenarios/MessageService.md b/contracts/test/V2 Scenarios/MessageService.md
new file mode 100644
index 000000000..fe8f09f89
--- /dev/null
+++ b/contracts/test/V2 Scenarios/MessageService.md
@@ -0,0 +1,93 @@
+# Message Service Test Scenarios
+
+
+## Roles
+
+`LIMIT_MANAGER` controls daily withdrawal limits
+
+`CONTRACT_UPGRADER` controls contract limits
+
+- TimelockController for upgrades `CONTRACT_UPGRADER`
+- Multisig for upgrades `LIMIT_MANAGER`
+
+## Tests
+
+### Initialize tests
+ - Access control is set for `CONTRACT_UPGRADER`
+ - Access control is set for `LIMIT_MANAGER`
+ - Can't initialise a second time
+ - `pauseManagerAddress` not `zero address`
+ - `pauseManagerAddress` is set
+
+### Access control tests
+- add account to role ?
+- remove account from role?
+- cannot remove self as admin ??
+- cannot set `self` as `CONTRACT_UPGRADER`
+- cannot set `zero address` as `CONTRACT_UPGRADER`
+- cannot set `self` as `LIMIT_MANAGER`
+- cannot set `zero address` as `LIMIT_MANAGER`
+
+### Upgrades
+- can initiate upgrade
+ - **note**: this has to be the TimeLockController
+
+
+### Withdrawal Limits
+- Can set limits as `LIMIT_MANAGER`
+- Cannot set limits as non-`LIMIT_MANAGER`
+
+### Send Message tests
+- `paused` contract `reverts`
+
+ **If not paused:**
+- hash exists reverts
+- value checks
+ - `_value` sent + `_fee` = `msg.value`
+- `_destinationChainId` allowed ( is there an allowed network list )
+- `_to` is not empty
+- `_value` and `_calldata` are both empty (pointless transport) ??
+- `_nonce` is default and hash does not exist
+- `MessageSent` is emitted on success
+ - indexed params are set for topics
+- `_destinationChainId` not same as `block.chainId`
+- cannot reenter on send with same hash
+- `cannot send` a message with a value if daily `withdrawal limit is reached`
+- `can send` a message with a value if daily `withdrawal limit is not reached`
+
+- **CRITICAL HACK HERE IF DONE WRONG** `cannot set destination` when destination is message manager on other layer - reverts
+- **CRITICAL HACK HERE IF DONE WRONG** `cannot set destination` when destination is message manager proxy on other layer - reverts
+
+### Claim Message tests
+- `paused` contract `reverts`
+
+ **If not paused:**
+- hash does not exist reverts
+- hash exists and is pending succeeds
+- hash exists and is delivered reverts
+- `BridgeMessageClaimed` emitted on success
+- `_destinationChainId` matches `block.chainId`
+- Call permutation tests
+ - error on ETH send only reverts
+ - error on contract call only reverts
+ - error on contract call with ETH send reverts
+ - no error on ETH send only does not revert
+ - no error on contract call only does not revert
+ - no error on contract call with ETH send does not rever
+ - `zero fee` does not try send ETH to fee recipient
+ - error on ETH Fee send reverts
+ - no error on ETH Fee send does not revert
+- `cannot claim` a message with a value if daily `withdrawal limit is reached`
+- `can claim` a message with a value if daily `withdrawal limit is not reached`
+- delivery `fee` and sent `value` increments daily withdrawal amounts used
+- `_feeRecipient` is `zero address`, the fee is sent to `msg.sender`
+- `_feeRecipient` is `not zero address`, , the fee is sent to `_feeRecipient`
+
+### Reentry
+- `cannot` enter with `claimMessage` -> `claimMessage`
+- `can` enter `claimMessage` -> `sendMessage`
+
+### setting networks tests
+- can add network
+- network exists reverts
+- not empty value
\ No newline at end of file
diff --git a/contracts/test/V2 Scenarios/PauseManager.md b/contracts/test/V2 Scenarios/PauseManager.md
new file mode 100644
index 000000000..47d432d8a
--- /dev/null
+++ b/contracts/test/V2 Scenarios/PauseManager.md
@@ -0,0 +1,7 @@
+# Pause Manager Test Scenarios
+
+## Pausing
+- `can pause` as `PAUSE_MANAGER_ROLE`
+- `cannot pause` as non-`PAUSE_MANAGER_ROLE`
+- `can unpause` as `PAUSE_MANAGER_ROLE`
+- `cannot unpause` as non-`PAUSE_MANAGER_ROLE`
\ No newline at end of file
diff --git a/contracts/test/ZkEvmV2.ts b/contracts/test/ZkEvmV2.ts
new file mode 100644
index 000000000..82aaf0769
--- /dev/null
+++ b/contracts/test/ZkEvmV2.ts
@@ -0,0 +1,875 @@
+import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
+import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
+import { expect } from "chai";
+import { BigNumber, ContractTransaction } from "ethers";
+import { ethers } from "hardhat";
+import { TestZkEvmV2, ZkEvmV2__factory } from "../typechain-types";
+import {
+ BAD_STARTING_HASH,
+ DEFAULT_ADMIN_ROLE,
+ GENERAL_PAUSE_TYPE,
+ INITIAL_MIGRATION_BLOCK,
+ INITIAL_WITHDRAW_LIMIT,
+ ONE_DAY_IN_SECONDS,
+ OPERATOR_ROLE,
+ OUTBOX_STATUS_RECEIVED,
+ OUTBOX_STATUS_SENT,
+ OUTBOX_STATUS_UNKNOWN,
+ PROVING_SYSTEM_PAUSE_TYPE,
+} from "./utils/constants";
+import { deployUpgradableFromFactory } from "./utils/deployment";
+import { getProverTestData, getTransactionsToBeDecoded } from "./utils/helpers";
+
+describe("ZK EVM V2 contract", () => {
+ let zkEvm: TestZkEvmV2;
+ let multiRollupZkEvm: TestZkEvmV2;
+
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ let admin: SignerWithAddress;
+ let verifier: string;
+ let securityCouncil: SignerWithAddress;
+ let operator: SignerWithAddress;
+ let nonAuthorizedAccount: SignerWithAddress;
+ let account: SignerWithAddress;
+
+ const { proof, blocks, parentStateRootHash, firstBlockNumber } = getProverTestData("Light", "output-file.json");
+ const {
+ proof: proofRollup1,
+ blocks: blocksRollup1,
+ parentStateRootHash: parentStateRootHashRollup1,
+ firstBlockNumber: firstBlockNumberRollup1,
+ } = getProverTestData("Light", "rollup-1.json");
+ const {
+ proof: proofRollup2,
+ blocks: blocksRollup2,
+ parentStateRootHash: parentStateRootHashRollup2,
+ } = getProverTestData("Light", "rollup-2.json");
+
+ async function deployZkEvmFixture() {
+ const PlonkVerifierFactory = await ethers.getContractFactory("PlonkVerifier");
+ const plonkVerifier = await PlonkVerifierFactory.deploy();
+ await plonkVerifier.deployed();
+
+ verifier = plonkVerifier.address;
+
+ const multiRollupZkEvm = (await deployUpgradableFromFactory(
+ "TestZkEvmV2",
+ [
+ parentStateRootHashRollup1,
+ firstBlockNumberRollup1 - 1,
+ verifier,
+ securityCouncil.address,
+ [operator.address],
+ ONE_DAY_IN_SECONDS,
+ INITIAL_WITHDRAW_LIMIT,
+ ],
+ {
+ initializer: "initialize(bytes32,uint256,address,address,address[],uint256,uint256)",
+ unsafeAllow: ["constructor"],
+ },
+ )) as TestZkEvmV2;
+
+ const zkEvm = (await deployUpgradableFromFactory(
+ "TestZkEvmV2",
+ [
+ parentStateRootHash,
+ firstBlockNumber - 1,
+ verifier,
+ securityCouncil.address,
+ [operator.address],
+ ONE_DAY_IN_SECONDS,
+ INITIAL_WITHDRAW_LIMIT,
+ ],
+ {
+ initializer: "initialize(bytes32,uint256,address,address,address[],uint256,uint256)",
+ unsafeAllow: ["constructor"],
+ },
+ )) as TestZkEvmV2;
+
+ return { zkEvm, multiRollupZkEvm };
+ }
+
+ before(async () => {
+ [admin, securityCouncil, operator, nonAuthorizedAccount, account] = await ethers.getSigners();
+ });
+
+ beforeEach(async () => {
+ const contracts = await loadFixture(deployZkEvmFixture);
+ zkEvm = contracts.zkEvm;
+ multiRollupZkEvm = contracts.multiRollupZkEvm;
+ });
+
+ describe("Initialisation", () => {
+ const zkEvmInterface = ZkEvmV2__factory.createInterface();
+
+ it("Should revert if verifier address is zero address ", async () => {
+ await expect(
+ deployUpgradableFromFactory(
+ "TestZkEvmV2",
+ [
+ parentStateRootHash,
+ INITIAL_MIGRATION_BLOCK,
+ ethers.constants.AddressZero,
+ securityCouncil.address,
+ [operator.address],
+ ONE_DAY_IN_SECONDS,
+ INITIAL_WITHDRAW_LIMIT,
+ ],
+ {
+ initializer: "initialize(bytes32,uint256,address,address,address[],uint256,uint256)",
+ unsafeAllow: ["constructor"],
+ },
+ ),
+ ).to.be.revertedWithCustomError({ interface: zkEvmInterface }, "ZeroAddressNotAllowed");
+ });
+
+ it("Should set the initial block number ", async () => {
+ const zkEvmContract = await deployUpgradableFromFactory(
+ "TestZkEvmV2",
+ [
+ parentStateRootHash,
+ 12345,
+ verifier,
+ securityCouncil.address,
+ [operator.address],
+ ONE_DAY_IN_SECONDS,
+ INITIAL_WITHDRAW_LIMIT,
+ ],
+ {
+ initializer: "initialize(bytes32,uint256,address,address,address[],uint256,uint256)",
+ unsafeAllow: ["constructor"],
+ },
+ );
+
+ expect(await zkEvmContract.currentL2BlockNumber()).to.be.equal(12345);
+ });
+
+ it("Should revert if an operator address is zero address ", async () => {
+ await expect(
+ deployUpgradableFromFactory(
+ "TestZkEvmV2",
+ [
+ parentStateRootHash,
+ INITIAL_MIGRATION_BLOCK,
+ verifier,
+ securityCouncil.address,
+ [ethers.constants.AddressZero],
+ ONE_DAY_IN_SECONDS,
+ INITIAL_WITHDRAW_LIMIT,
+ ],
+ {
+ initializer: "initialize(bytes32,uint256,address,address,address[],uint256,uint256)",
+ unsafeAllow: ["constructor"],
+ },
+ ),
+ ).to.be.revertedWithCustomError({ interface: zkEvmInterface }, "ZeroAddressNotAllowed");
+ });
+
+ it("Should store verifier address in storage ", async () => {
+ const { zkEvm } = await loadFixture(deployZkEvmFixture);
+ expect(await zkEvm.verifiers(0)).to.be.equal(verifier);
+ });
+
+ it("Should assign the OPERATOR_ROLE to operator addresses", async () => {
+ const { zkEvm } = await loadFixture(deployZkEvmFixture);
+ expect(await zkEvm.hasRole(OPERATOR_ROLE, operator.address)).to.be.true;
+ });
+
+ it("Should store the startingRootHash in storage for the first block number", async () => {
+ const { zkEvm } = await loadFixture(deployZkEvmFixture);
+ expect(await zkEvm.stateRootHashes(firstBlockNumber - 1)).to.be.equal(parentStateRootHash);
+ });
+
+ it("Should revert if the initialize function is called a second time", async () => {
+ const { zkEvm } = await loadFixture(deployZkEvmFixture);
+ await expect(
+ zkEvm.initialize(
+ parentStateRootHash,
+ INITIAL_MIGRATION_BLOCK,
+ verifier,
+ securityCouncil.address,
+ [operator.address],
+ ONE_DAY_IN_SECONDS,
+ INITIAL_WITHDRAW_LIMIT,
+ ),
+ ).to.be.revertedWith("Initializable: contract is already initialized");
+ });
+ });
+
+ describe("When not paused", () => {
+ describe("Change verifier address", () => {
+ it("Should revert if the caller has not the DEFAULT_ADMIN_ROLE", async () => {
+ await expect(zkEvm.connect(nonAuthorizedAccount).setVerifierAddress(account.address, 2)).to.be.revertedWith(
+ `AccessControl: account ${nonAuthorizedAccount.address.toLowerCase()} is missing role ${DEFAULT_ADMIN_ROLE}`,
+ );
+ });
+
+ it("Should revert if the address being set is the zero address", async () => {
+ await expect(
+ zkEvm.connect(securityCouncil).setVerifierAddress(ethers.constants.AddressZero, 2),
+ ).to.be.revertedWithCustomError(zkEvm, "ZeroAddressNotAllowed");
+ });
+
+ it("Should revert if the address being set is the zero address", async () => {
+ await expect(
+ zkEvm.connect(securityCouncil).setVerifierAddress(ethers.constants.AddressZero, 2),
+ ).to.be.revertedWithCustomError(zkEvm, "ZeroAddressNotAllowed");
+ });
+
+ it("Should set the new verifier address", async () => {
+ await zkEvm.connect(securityCouncil).setVerifierAddress(account.address, 2);
+ expect(await zkEvm.verifiers(2)).to.be.equal(account.address);
+ });
+
+ it("Should emit the correct event", async () => {
+ await zkEvm.connect(securityCouncil).setVerifierAddress(account.address, 2);
+ expect(await zkEvm.verifiers(2))
+ .to.emit(zkEvm, "VerifierAddressChanged")
+ .withArgs(account.address, 2, securityCouncil.address);
+ });
+ });
+
+ describe("Multiple in a row with light proof", () => {
+ it("Should fail when starting rootHash does not match last known block starting hash", async () => {
+ await expect(
+ multiRollupZkEvm
+ .connect(operator)
+ .finalizeBlocks(blocksRollup1, proof, 0, BAD_STARTING_HASH, { gasLimit: 10_000_000 }),
+ ).to.be.revertedWithCustomError(multiRollupZkEvm, "StartingRootHashDoesNotMatch");
+ });
+
+ it("Should finalize multiple rollups and change the current timestamp", async () => {
+ const { currentTimestampAfterSecondCall } = await finalizeMultipleBlocks();
+ expect(currentTimestampAfterSecondCall.toNumber()).to.equal(
+ blocksRollup2[blocksRollup2.length - 1].l2BlockTimestamp,
+ );
+ });
+
+ it("Should finalize multiple rollups and change the current block number", async () => {
+ const { initialCurrentBlockNumber, currentBlockNumberAfterSecondCall } = await finalizeMultipleBlocks();
+ expect(currentBlockNumberAfterSecondCall).to.equal(
+ initialCurrentBlockNumber.add(blocksRollup1.length).add(blocksRollup2.length),
+ );
+ });
+
+ it("Should finalize multiple rollups and set the root hashes for each block", async () => {
+ const { initialCurrentBlockNumber, currentBlockNumberAfterFirstCall } = await finalizeMultipleBlocks();
+
+ const lastStateRootHashForFirstCall = await multiRollupZkEvm.stateRootHashes(
+ initialCurrentBlockNumber.add(blocksRollup1.length),
+ );
+ expect(lastStateRootHashForFirstCall).to.equal(blocksRollup1[blocksRollup1.length - 1].blockRootHash);
+
+ const lastStateRootHashForSecondCall = await multiRollupZkEvm.stateRootHashes(
+ currentBlockNumberAfterFirstCall.add(blocksRollup2.length),
+ );
+ expect(lastStateRootHashForSecondCall).to.equal(blocksRollup2[blocksRollup2.length - 1].blockRootHash);
+ });
+
+ it("Should finalize multiple rollups and emit BlockFinalized", async () => {
+ const {
+ firstRollupTransaction,
+ secondRollupTransaction,
+ initialCurrentBlockNumber,
+ currentBlockNumberAfterFirstCall,
+ } = await finalizeMultipleBlocks();
+
+ let blockNumber = initialCurrentBlockNumber.toNumber();
+ let expectedEventData = blocksRollup1.map(({ blockRootHash }) => {
+ blockNumber++;
+ return { blockNumber, stateRootHash: blockRootHash };
+ });
+
+ const { events } = await firstRollupTransaction.wait();
+
+ expect(events).to.not.be.undefined;
+
+ if (events) {
+ const filteredEvents = events.filter((event) => event.event === "BlockFinalized");
+ expect(filteredEvents.length).to.be.equal(expectedEventData.length);
+
+ // check for topic with non empty data
+ for (let i = 0; i < filteredEvents.length; i++) {
+ expect(filteredEvents[i].args?.blockNumber).to.deep.equal(expectedEventData[i].blockNumber);
+ expect(filteredEvents[i].args?.stateRootHash).to.deep.equal(expectedEventData[i].stateRootHash);
+ }
+ }
+
+ blockNumber = currentBlockNumberAfterFirstCall.toNumber();
+ expectedEventData = blocksRollup2.map(({ blockRootHash }) => {
+ blockNumber++;
+ return { blockNumber, stateRootHash: blockRootHash };
+ });
+
+ const { events: eventsSet2 } = await secondRollupTransaction.wait();
+
+ expect(eventsSet2).to.not.be.undefined;
+
+ if (eventsSet2) {
+ const filteredEvents = eventsSet2.filter((event) => event.event === "BlockFinalized");
+ expect(filteredEvents.length).to.be.equal(expectedEventData.length);
+
+ // check for topic with non empty data
+ for (let i = 0; i < filteredEvents.length; i++) {
+ expect(filteredEvents[i].args?.blockNumber).to.deep.equal(expectedEventData[i].blockNumber);
+ expect(filteredEvents[i].args?.stateRootHash).to.deep.equal(expectedEventData[i].stateRootHash);
+ }
+ }
+ });
+
+ it("Should finalize multiple blocks and emit BlocksVerificationDone", async () => {
+ const {
+ firstRollupTransaction,
+ secondRollupTransaction,
+ currentBlockNumberAfterFirstCall,
+ currentBlockNumberAfterSecondCall,
+ } = await finalizeMultipleBlocks();
+
+ await expect(firstRollupTransaction)
+ .to.emit(multiRollupZkEvm, "BlocksVerificationDone")
+ // blocks are zero based, based on initial data and this should be 1 less
+ .withArgs(
+ currentBlockNumberAfterFirstCall,
+ parentStateRootHashRollup1,
+ blocksRollup1[blocksRollup1.length - 1].blockRootHash,
+ );
+
+ await expect(secondRollupTransaction)
+ .to.emit(multiRollupZkEvm, "BlocksVerificationDone")
+ // blocks are zero based, based on initial data and this should be 1 less
+ .withArgs(
+ currentBlockNumberAfterSecondCall,
+ parentStateRootHashRollup2,
+ blocksRollup2[blocksRollup2.length - 1].blockRootHash,
+ );
+ });
+ });
+
+ describe("finalizeBlocks with light proof", () => {
+ it("Should finalize blocks", async () => {
+ const txHashes: string[][] = [];
+ for (const tx of getTransactionsToBeDecoded(blocks)) {
+ const txs = await zkEvm.extractMessageHashes(tx);
+ txHashes.push(txs);
+ await zkEvm.addL1L2MessageHash(tx);
+ }
+
+ const tx = await zkEvm
+ .connect(operator)
+ .finalizeBlocks(blocks, proof, 0, parentStateRootHash, { gasLimit: 10_000_000 });
+ const { events } = await tx.wait();
+
+ expect(events).to.not.be.undefined;
+
+ if (events) {
+ const filteredEvents = events.filter((event) => event.event === "L1L2MessagesReceivedOnL2");
+ expect(filteredEvents.length).to.equal(txHashes.length);
+
+ for (let i = 0; i < filteredEvents.length; i++) {
+ expect(filteredEvents[i].args?.messageHashes).to.deep.equal(txHashes[i]);
+ }
+ }
+ });
+
+ it("Should finalize blocks and change currentL2BlockNumber", async () => {
+ for (const tx of getTransactionsToBeDecoded(blocks)) {
+ await zkEvm.addL1L2MessageHash(tx);
+ }
+
+ const currentBlockNumberKnownBeforeExecution = await zkEvm.currentL2BlockNumber();
+ expect(currentBlockNumberKnownBeforeExecution).to.equal(firstBlockNumber - 1);
+
+ await zkEvm.connect(operator).finalizeBlocks(blocks, proof, 0, parentStateRootHash, { gasLimit: 10_000_000 });
+
+ const currentBlockNumberKnownAfterExecution = await zkEvm.currentL2BlockNumber();
+ expect(currentBlockNumberKnownAfterExecution).to.equal(
+ currentBlockNumberKnownBeforeExecution.add(blocks.length),
+ );
+ });
+
+ it("Should finalize blocks and change currentTimestamp", async () => {
+ for (const tx of getTransactionsToBeDecoded(blocks)) {
+ await zkEvm.addL1L2MessageHash(tx);
+ }
+
+ let currentTimestamp = await zkEvm.currentTimestamp();
+ expect(currentTimestamp).to.equal(0);
+
+ await zkEvm.connect(operator).finalizeBlocks(blocks, proof, 0, parentStateRootHash, { gasLimit: 10_000_000 });
+
+ currentTimestamp = await zkEvm.currentTimestamp();
+ expect(currentTimestamp).to.equal(blocks[blocks.length - 1].l2BlockTimestamp);
+ });
+
+ it("Should finalize blocks and emit BlockFinalized", async () => {
+ for (const tx of getTransactionsToBeDecoded(blocks)) {
+ await zkEvm.addL1L2MessageHash(tx);
+ }
+
+ let blockNumber = firstBlockNumber - 1;
+ const expectedEventData = blocks.map(({ blockRootHash }) => {
+ blockNumber++;
+ return { blockNumber, stateRootHash: blockRootHash };
+ });
+
+ const tx = await zkEvm
+ .connect(operator)
+ .finalizeBlocks(blocks, proof, 0, parentStateRootHash, { gasLimit: 10_000_000 });
+
+ const { events } = await tx.wait();
+
+ expect(events).to.not.be.undefined;
+
+ if (events) {
+ const filteredEvents = events.filter((event) => event.event === "BlockFinalized");
+ expect(filteredEvents.length).to.be.equal(expectedEventData.length);
+
+ // check for topic with non empty data
+ for (let i = 0; i < filteredEvents.length; i++) {
+ expect(filteredEvents[i].args?.blockNumber).to.deep.equal(expectedEventData[i].blockNumber);
+ expect(filteredEvents[i].args?.stateRootHash).to.deep.equal(expectedEventData[i].stateRootHash);
+ }
+ }
+ });
+
+ it("Should finalize blocks and emit BlocksVerificationDone", async () => {
+ for (const tx of getTransactionsToBeDecoded(blocks)) {
+ await zkEvm.addL1L2MessageHash(tx);
+ }
+ const previousKnownStartingNumber = await zkEvm.currentL2BlockNumber();
+ await expect(
+ zkEvm.connect(operator).finalizeBlocks(blocks, proof, 0, parentStateRootHash, { gasLimit: 10_000_000 }),
+ )
+ .to.emit(zkEvm, "BlocksVerificationDone")
+ // blocks are zero based, based on initial data and this should be 1 less
+ .withArgs(
+ previousKnownStartingNumber.add(blocks.length),
+ parentStateRootHash,
+ blocks[blocks.length - 1].blockRootHash,
+ );
+ });
+
+ it("Should fail to process with future BlockTimeStamp", async () => {
+ const { blocks } = getProverTestData("Light", "output-file.json");
+
+ for (const tx of getTransactionsToBeDecoded(blocks)) {
+ await zkEvm.addL1L2MessageHash(tx);
+ }
+
+ blocks[blocks.length - 1].l2BlockTimestamp = 3123456789;
+ await expect(
+ zkEvm.connect(operator).finalizeBlocks(blocks, proof, 0, parentStateRootHash, { gasLimit: 10_000_000 }),
+ ).to.be.revertedWithCustomError(zkEvm, "BlockTimestampError");
+ });
+
+ it("Should fail to process with duplicate data", async () => {
+ for (const tx of getTransactionsToBeDecoded(blocks)) {
+ await zkEvm.addL1L2MessageHash(tx);
+ }
+
+ await expect(
+ zkEvm
+ .connect(operator)
+ .finalizeBlocks([...blocks, ...blocks], proof, 0, parentStateRootHash, { gasLimit: 15_000_000 }),
+ ).to.be.revertedWithCustomError(zkEvm, "MessageAlreadyReceived");
+ });
+
+ it("Should fail when messages not marked as sent", async () => {
+ await expect(
+ zkEvm.connect(operator).finalizeBlocks(blocks, proof, 0, parentStateRootHash, { gasLimit: 10_000_000 }),
+ ).to.be.revertedWithCustomError(zkEvm, "L1L2MessageNotSent");
+ });
+
+ it("Should fail when proof does not match", async () => {
+ for (const tx of getTransactionsToBeDecoded(blocks)) {
+ await zkEvm.addL1L2MessageHash(tx);
+ }
+
+ const data = getProverTestData("Light", "output-file.json");
+ // remove a transaction breaking the hashes
+ data.blocks[0].transactions.pop();
+
+ await expect(
+ zkEvm.connect(operator).finalizeBlocks(data.blocks, proof, 0, parentStateRootHash, { gasLimit: 10_000_000 }),
+ ).to.be.revertedWithCustomError(zkEvm, "InvalidProof");
+ });
+
+ it("Should set the state hash for each block", async () => {
+ for (const tx of getTransactionsToBeDecoded(blocks)) {
+ await zkEvm.addL1L2MessageHash(tx);
+ }
+ const previousKnownStartingNumber = await zkEvm.currentL2BlockNumber();
+
+ await zkEvm.connect(operator).finalizeBlocks(blocks, proof, 0, parentStateRootHash, { gasLimit: 10_000_000 });
+
+ const blockHash = await zkEvm.stateRootHashes(previousKnownStartingNumber.add(blocks.length));
+ expect(blockHash).to.equal(blocks[blocks.length - 1].blockRootHash);
+ });
+
+ it("Should revert when proofType is invalid", async () => {
+ for (const tx of getTransactionsToBeDecoded(blocks)) {
+ await zkEvm.addL1L2MessageHash(tx);
+ }
+
+ await expect(
+ zkEvm.connect(operator).finalizeBlocks(blocks, proof, 2, parentStateRootHash, { gasLimit: 10_000_000 }),
+ ).to.be.revertedWithCustomError(zkEvm, "InvalidProofType");
+ });
+
+ it("Should fail when starting rootHash does not match last known block starting hash", async () => {
+ await expect(
+ zkEvm.connect(operator).finalizeBlocks(blocks, proof, 0, BAD_STARTING_HASH, { gasLimit: 10_000_000 }),
+ ).to.be.revertedWithCustomError(zkEvm, "StartingRootHashDoesNotMatch");
+ });
+
+ it("Cannot call 'verify without proof' missing admin role", async () => {
+ await expect(
+ zkEvm
+ .connect(nonAuthorizedAccount)
+ .finalizeBlocks(blocks, proof, 0, parentStateRootHash, { gasLimit: 10_000_000 }),
+ ).to.be.revertedWith(
+ "AccessControl: account " + nonAuthorizedAccount.address.toLowerCase() + " is missing role " + OPERATOR_ROLE,
+ );
+ });
+ });
+
+ describe("finalize blocks without proof", () => {
+ it("Should execute without failure", async () => {
+ for (const tx of getTransactionsToBeDecoded(blocks)) {
+ await zkEvm.addL1L2MessageHash(tx);
+ }
+
+ await expect(
+ zkEvm.connect(securityCouncil).finalizeBlocksWithoutProof(blocks, { gasLimit: 10_000_000 }),
+ ).to.emit(zkEvm, "L2L1MessageHashAddedToInbox");
+ });
+
+ it("Should execute without failure", async () => {
+ for (const tx of getTransactionsToBeDecoded(blocks)) {
+ await zkEvm.addL1L2MessageHash(tx);
+ }
+
+ await expect(
+ zkEvm.connect(securityCouncil).finalizeBlocksWithoutProof(blocks, { gasLimit: 10_000_000 }),
+ ).to.emit(zkEvm, "L2L1MessageHashAddedToInbox");
+ });
+
+ it("Should finalize blocks and change currentL2BlockNumber", async () => {
+ for (const tx of getTransactionsToBeDecoded(blocks)) {
+ await zkEvm.addL1L2MessageHash(tx);
+ }
+
+ const currentBlockNumberKnownBeforeExecution = await zkEvm.currentL2BlockNumber();
+ expect(currentBlockNumberKnownBeforeExecution).to.equal(firstBlockNumber - 1);
+
+ await zkEvm.connect(securityCouncil).finalizeBlocksWithoutProof(blocks, { gasLimit: 10_000_000 });
+
+ const currentBlockNumberKnownAfterExecution = await zkEvm.currentL2BlockNumber();
+ expect(currentBlockNumberKnownAfterExecution).to.equal(
+ currentBlockNumberKnownBeforeExecution.add(blocks.length),
+ );
+ });
+
+ it("Should finalize blocks and change currentTimestamp", async () => {
+ for (const tx of getTransactionsToBeDecoded(blocks)) {
+ await zkEvm.addL1L2MessageHash(tx);
+ }
+
+ const currentTimestampBeforeExecution = await zkEvm.currentTimestamp();
+ expect(currentTimestampBeforeExecution).to.equal(0);
+
+ await zkEvm.connect(securityCouncil).finalizeBlocksWithoutProof(blocks, { gasLimit: 10_000_000 });
+
+ const currentTimestampAfterExecution = await zkEvm.currentTimestamp();
+ expect(currentTimestampAfterExecution).to.equal(blocks[blocks.length - 1].l2BlockTimestamp);
+ });
+
+ it("Should set the state hash for each block", async () => {
+ for (const tx of getTransactionsToBeDecoded(blocks)) {
+ await zkEvm.addL1L2MessageHash(tx);
+ }
+ const previousKnownCurrentBlockNumber = await zkEvm.currentL2BlockNumber();
+
+ await zkEvm.connect(securityCouncil).finalizeBlocksWithoutProof(blocks, { gasLimit: 10_000_000 });
+
+ const blockHash = await zkEvm.stateRootHashes(previousKnownCurrentBlockNumber.add(blocks.length));
+ expect(blockHash).to.equal(blocks[blocks.length - 1].blockRootHash);
+ });
+
+ it("Cannot call 'verify without proof' missing admin role", async () => {
+ await expect(
+ zkEvm.connect(nonAuthorizedAccount).finalizeBlocksWithoutProof(blocks, { gasLimit: 10_000_000 }),
+ ).to.be.revertedWith(
+ "AccessControl: account " +
+ nonAuthorizedAccount.address.toLowerCase() +
+ " is missing role " +
+ DEFAULT_ADMIN_ROLE,
+ );
+ });
+ });
+ });
+
+ describe("Block processing directly", () => {
+ describe("process block transactions", () => {
+ it("Should succeed and emit expected L1L2MessagesReceivedOnL2 events", async () => {
+ const hashesToExpect: string[][] = [];
+
+ for (const tx of getTransactionsToBeDecoded(blocks)) {
+ const hashes = await zkEvm.extractMessageHashes(tx);
+ hashesToExpect.push(hashes);
+
+ expect(hashes).to.not.be.empty; // be sure we are not checking against an empty set
+
+ for (const hash of hashes) {
+ expect(await zkEvm.outboxL1L2MessageStatus(hash)).to.be.equal(OUTBOX_STATUS_UNKNOWN);
+ }
+
+ await zkEvm.addL1L2MessageHash(tx);
+
+ for (const hash of hashes) {
+ expect(await zkEvm.outboxL1L2MessageStatus(hash)).to.be.equal(OUTBOX_STATUS_SENT);
+ }
+ }
+
+ for (const block of blocks) {
+ const tx = await zkEvm
+ .connect(operator)
+ .processBlockTransactions(block.transactions, block.batchReceptionIndices, { gasLimit: 10_000_000 });
+
+ const { events } = await tx.wait();
+
+ const txsToBeDecoded = getTransactionsToBeDecoded([block]);
+ let blockTxHashes: string[][] = [];
+ for (const tx of txsToBeDecoded) {
+ blockTxHashes = [...blockTxHashes, await zkEvm.extractMessageHashes(tx)];
+ }
+
+ await Promise.all([blockTxHashes]);
+
+ expect(events).to.not.be.undefined;
+
+ if (events) {
+ const filteredEvents = events.filter((event) => event.event === "L1L2MessagesReceivedOnL2");
+ expect(filteredEvents.length).to.equal(blockTxHashes.length);
+
+ for (let i = 0; i < filteredEvents.length; i++) {
+ expect(filteredEvents[i].args?.messageHashes).to.deep.equal(blockTxHashes[i]);
+ }
+ }
+ }
+
+ for (const blockTx of getTransactionsToBeDecoded(blocks)) {
+ const blockTxHashes = await zkEvm.extractMessageHashes(blockTx);
+ for (const blockTxHash of blockTxHashes) {
+ expect(await zkEvm.outboxL1L2MessageStatus(blockTxHash)).to.be.equal(OUTBOX_STATUS_RECEIVED);
+ }
+ }
+ });
+
+ it("Should succeed idempotently and emit expected L1L2MessagesReceivedOnL2 events", async () => {
+ const hashesToExpect: string[][] = [];
+
+ for (const tx of getTransactionsToBeDecoded(blocks)) {
+ const hashes = await zkEvm.extractMessageHashes(tx);
+ hashesToExpect.push(hashes);
+
+ expect(hashes).to.not.be.empty; // be sure we are not checking against an empty set
+
+ for (const hash of hashes) {
+ expect(await zkEvm.outboxL1L2MessageStatus(hash)).to.be.equal(OUTBOX_STATUS_UNKNOWN);
+ }
+
+ await zkEvm.addL1L2MessageHash(tx);
+
+ for (const hash of hashes) {
+ expect(await zkEvm.outboxL1L2MessageStatus(hash)).to.be.equal(OUTBOX_STATUS_SENT);
+ }
+ }
+
+ for (const block of blocks) {
+ let tx = await zkEvm
+ .connect(operator)
+ .processBlockTransactions(block.transactions, block.batchReceptionIndices, { gasLimit: 10_000_000 });
+
+ // call it a second time to exercise the conditional branch
+ tx = await zkEvm
+ .connect(operator)
+ .processBlockTransactions(block.transactions, block.batchReceptionIndices, { gasLimit: 10_000_000 });
+
+ const { events } = await tx.wait();
+
+ const txsToBeDecoded = getTransactionsToBeDecoded([block]);
+ let blockTxHashes: string[][] = [];
+ for (const tx of txsToBeDecoded) {
+ blockTxHashes = [...blockTxHashes, await zkEvm.extractMessageHashes(tx)];
+ }
+
+ await Promise.all([blockTxHashes]);
+
+ expect(events).to.not.be.undefined;
+
+ if (events) {
+ const filteredEvents = events.filter((event) => event.event === "L1L2MessagesReceivedOnL2");
+ expect(filteredEvents.length).to.equal(blockTxHashes.length);
+
+ for (let i = 0; i < filteredEvents.length; i++) {
+ expect(filteredEvents[i].args?.messageHashes).to.deep.equal(blockTxHashes[i]);
+ }
+ }
+ }
+
+ for (const blockTx of getTransactionsToBeDecoded(blocks)) {
+ const blockTxHashes = await zkEvm.extractMessageHashes(blockTx);
+ for (const blockTxHash of blockTxHashes) {
+ expect(await zkEvm.outboxL1L2MessageStatus(blockTxHash)).to.be.equal(OUTBOX_STATUS_RECEIVED);
+ }
+ }
+ });
+
+ it("Should fail with EmptyBlock", async () => {
+ await expect(
+ zkEvm.connect(operator).processBlockTransactions([], [], { gasLimit: 10_000_000 }),
+ ).to.be.revertedWithCustomError(zkEvm, "EmptyBlock");
+ });
+ });
+
+ describe("process block logs", () => {
+ it("Should succeed and emit expected L2L1MessageHashAddedToInbox events", async () => {
+ let hasLogs = false;
+
+ for (const txs of blocks) {
+ if (txs.l2ToL1MsgHashes.length !== 0) {
+ const hashes = txs.l2ToL1MsgHashes;
+ expect(hashes).to.not.be.empty; // be sure we are not checking against an empty set
+
+ const tx = await zkEvm
+ .connect(operator)
+ .processMessageHashes(txs.l2ToL1MsgHashes, { gasLimit: 10_000_000 });
+
+ const { events } = await tx.wait();
+ expect(events).to.not.be.undefined;
+
+ if (events) {
+ const filteredEvents = events.filter((event) => event.event === "L2L1MessageHashAddedToInbox");
+ for (let i = 0; i < filteredEvents.length; i++) {
+ expect(filteredEvents[i].args?.messageHash).to.deep.equal(hashes[i]);
+ }
+ }
+ hasLogs = true;
+ }
+ }
+ expect(hasLogs).to.be.true;
+ });
+
+ it("Should fail with EmptyBlock", async () => {
+ await expect(
+ zkEvm.connect(operator).processBlockTransactions([], [], { gasLimit: 10_000_000 }),
+ ).to.be.revertedWithCustomError(zkEvm, "EmptyBlock");
+ });
+ });
+ });
+
+ describe("When paused", () => {
+ describe("When generally paused", () => {
+ it("Should fail to finalize without proof", async () => {
+ await zkEvm.connect(securityCouncil).pauseByType(GENERAL_PAUSE_TYPE);
+
+ await expect(zkEvm.connect(securityCouncil).finalizeBlocksWithoutProof(blocks, { gasLimit: 10_000_000 }))
+ .to.be.revertedWithCustomError(zkEvm, "IsPaused")
+ .withArgs(GENERAL_PAUSE_TYPE);
+ });
+
+ it("Should fail to finalize", async () => {
+ await zkEvm.connect(securityCouncil).pauseByType(GENERAL_PAUSE_TYPE);
+ await expect(
+ zkEvm.connect(operator).finalizeBlocks(blocks, proof, 0, parentStateRootHash, { gasLimit: 10_000_000 }),
+ )
+ .to.be.revertedWithCustomError(zkEvm, "IsPaused")
+ .withArgs(GENERAL_PAUSE_TYPE);
+ });
+ });
+
+ describe("When specifically paused", () => {
+ it("Should fail to finalize", async () => {
+ await zkEvm.connect(securityCouncil).pauseByType(PROVING_SYSTEM_PAUSE_TYPE);
+ await expect(
+ zkEvm.connect(operator).finalizeBlocks(blocks, proof, 0, parentStateRootHash, { gasLimit: 10_000_000 }),
+ ).to.be.revertedWithCustomError(zkEvm, "IsPaused");
+ });
+ });
+ });
+
+ describe("Pausing and unpausing", () => {
+ it("Should Pause and unpause", async () => {
+ await zkEvm.connect(securityCouncil).pauseByType(GENERAL_PAUSE_TYPE);
+ let isPaused = await zkEvm.pauseTypeStatuses(GENERAL_PAUSE_TYPE);
+ expect(isPaused).to.be.true;
+
+ await zkEvm.connect(securityCouncil).unPauseByType(GENERAL_PAUSE_TYPE);
+ isPaused = await zkEvm.pauseTypeStatuses(GENERAL_PAUSE_TYPE);
+ expect(isPaused).to.be.false;
+ });
+ });
+
+ async function finalizeMultipleBlocks(): Promise<{
+ firstRollupTransaction: ContractTransaction;
+ secondRollupTransaction: ContractTransaction;
+ initialCurrentBlockNumber: BigNumber;
+ currentBlockNumberAfterFirstCall: BigNumber;
+ currentBlockNumberAfterSecondCall: BigNumber;
+ initialCurrentTimestamp: BigNumber;
+ currentTimestampAfterFirstCall: BigNumber;
+ currentTimestampAfterSecondCall: BigNumber;
+ }> {
+ const txHashes: string[][] = [];
+
+ for (const tx of getTransactionsToBeDecoded(blocksRollup1)) {
+ const txs = await multiRollupZkEvm.extractMessageHashes(tx);
+ txHashes.push(txs);
+ await multiRollupZkEvm.addL1L2MessageHash(tx);
+ }
+
+ const initialCurrentBlockNumber = await multiRollupZkEvm.currentL2BlockNumber();
+ expect(initialCurrentBlockNumber).to.equal(firstBlockNumberRollup1 - 1);
+
+ const initialCurrentTimestamp = await multiRollupZkEvm.currentTimestamp();
+ expect(initialCurrentTimestamp).to.equal(0);
+
+ const firstRollupTransaction = await multiRollupZkEvm
+ .connect(operator)
+ .finalizeBlocks(blocksRollup1, proofRollup1, 0, parentStateRootHashRollup1, { gasLimit: 10_000_000 });
+
+ const currentBlockNumberAfterFirstCall = await multiRollupZkEvm.currentL2BlockNumber();
+ expect(currentBlockNumberAfterFirstCall).to.equal(initialCurrentBlockNumber.add(blocksRollup1.length));
+
+ const currentTimestampAfterFirstCall = await multiRollupZkEvm.currentTimestamp();
+ expect(currentTimestampAfterFirstCall).to.equal(blocksRollup1[blocksRollup1.length - 1].l2BlockTimestamp);
+
+ for (const tx of getTransactionsToBeDecoded(blocksRollup2)) {
+ const txs = await multiRollupZkEvm.extractMessageHashes(tx);
+ txHashes.push(txs);
+ await multiRollupZkEvm.addL1L2MessageHash(tx);
+ }
+
+ const secondRollupTransaction = await multiRollupZkEvm
+ .connect(operator)
+ .finalizeBlocks(blocksRollup2, proofRollup2, 0, parentStateRootHashRollup2, { gasLimit: 10_000_000 });
+
+ const currentBlockNumberAfterSecondCall = await multiRollupZkEvm.currentL2BlockNumber();
+ expect(currentBlockNumberAfterSecondCall).to.equal(currentBlockNumberAfterFirstCall.add(blocksRollup2.length));
+
+ const currentTimestampAfterSecondCall = await multiRollupZkEvm.currentTimestamp();
+ expect(currentTimestampAfterSecondCall).to.equal(blocksRollup2[blocksRollup2.length - 1].l2BlockTimestamp);
+
+ return {
+ firstRollupTransaction,
+ secondRollupTransaction,
+ initialCurrentBlockNumber,
+ currentBlockNumberAfterFirstCall,
+ currentBlockNumberAfterSecondCall,
+ initialCurrentTimestamp,
+ currentTimestampAfterFirstCall,
+ currentTimestampAfterSecondCall,
+ };
+ }
+});
diff --git a/contracts/test/ZkEvmV2Init.ts b/contracts/test/ZkEvmV2Init.ts
new file mode 100644
index 000000000..746884fe8
--- /dev/null
+++ b/contracts/test/ZkEvmV2Init.ts
@@ -0,0 +1,75 @@
+import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
+import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
+import { expect } from "chai";
+import { ethers } from "hardhat";
+import { TestZkEvmV2, ZkEvmV2Init__factory } from "../typechain-types";
+import { INITIAL_WITHDRAW_LIMIT, ONE_DAY_IN_SECONDS } from "./utils/constants";
+import { deployUpgradableFromFactory } from "./utils/deployment";
+import { getProverTestData } from "./utils/helpers";
+
+describe("ZK EVM V2 contract", () => {
+ let zkEvm: TestZkEvmV2;
+
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ let admin: SignerWithAddress;
+ let verifier: string;
+ let securityCouncil: SignerWithAddress;
+ let operator: SignerWithAddress;
+
+ const { parentStateRootHash, firstBlockNumber } = getProverTestData("Light", "output-file.json");
+
+ async function deployZkEvmFixture() {
+ const PlonkVerifierFactory = await ethers.getContractFactory("PlonkVerifier");
+ const plonkVerifier = await PlonkVerifierFactory.deploy();
+ await plonkVerifier.deployed();
+
+ verifier = plonkVerifier.address;
+
+ const zkEvm = (await deployUpgradableFromFactory(
+ "TestZkEvmV2",
+ [
+ parentStateRootHash,
+ firstBlockNumber - 1,
+ verifier,
+ securityCouncil.address,
+ [operator.address],
+ ONE_DAY_IN_SECONDS,
+ INITIAL_WITHDRAW_LIMIT,
+ ],
+ {
+ initializer: "initialize(bytes32,uint256,address,address,address[],uint256,uint256)",
+ unsafeAllow: ["constructor"],
+ },
+ )) as TestZkEvmV2;
+
+ return { zkEvm };
+ }
+
+ before(async () => {
+ [admin, securityCouncil, operator] = await ethers.getSigners();
+ });
+
+ beforeEach(async () => {
+ const contracts = await loadFixture(deployZkEvmFixture);
+ zkEvm = contracts.zkEvm;
+ });
+
+ describe("Re-initialisation", () => {
+ ZkEvmV2Init__factory.createInterface();
+
+ it("Should set the initial block number ", async () => {
+ const l2block = ethers.BigNumber.from(12121);
+ const l2BlockNumber = await zkEvm.currentL2BlockNumber();
+ const zkEvmContract = await deployUpgradableFromFactory("ZkEvmV2Init", [l2block, parentStateRootHash], {
+ initializer: "initializeV2(uint256,bytes32)",
+ unsafeAllow: ["constructor"],
+ });
+ const currentL2BlockNumber = await zkEvmContract.currentL2BlockNumber();
+
+ expect(currentL2BlockNumber).to.be.equal(l2block);
+ expect(currentL2BlockNumber).to.not.be.equal(l2BlockNumber);
+ expect(await zkEvm.periodInSeconds()).to.be.equal(ONE_DAY_IN_SECONDS);
+ expect(zkEvmContract.stateRootHashes(l2block)).to.not.be.equal(zkEvm.stateRootHashes(parentStateRootHash));
+ });
+ });
+});
diff --git a/contracts/test/ZkEvmV2_Full.ts b/contracts/test/ZkEvmV2_Full.ts
new file mode 100644
index 000000000..647b94d7c
--- /dev/null
+++ b/contracts/test/ZkEvmV2_Full.ts
@@ -0,0 +1,439 @@
+import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
+import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
+import { expect } from "chai";
+import { BigNumber, ContractTransaction } from "ethers";
+import { ethers } from "hardhat";
+import { TestZkEvmV2 } from "../typechain-types";
+import { BAD_STARTING_HASH, INITIAL_WITHDRAW_LIMIT, ONE_DAY_IN_SECONDS } from "./utils/constants";
+import { deployUpgradableFromFactory } from "./utils/deployment";
+import { getProverTestData, getTransactionsToBeDecoded } from "./utils/helpers";
+
+describe("ZK EVM V2 contract with full verifier", () => {
+ let zkEvm: TestZkEvmV2;
+ let multiRollupZkEvm: TestZkEvmV2;
+
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ let admin: SignerWithAddress;
+ let verifier: string;
+ let securityCouncil: SignerWithAddress;
+ let operator: SignerWithAddress;
+
+ const PROOF_MODE = "Full";
+
+ const { proof, blocks, parentStateRootHash, firstBlockNumber } = getProverTestData(PROOF_MODE, "output-file.json");
+ const {
+ proof: proofRollup1,
+ blocks: blocksRollup1,
+ parentStateRootHash: parentStateRootHashRollup1,
+ firstBlockNumber: firstBlockNumberRollup1,
+ } = getProverTestData(PROOF_MODE, "rollup-1.json");
+ const {
+ proof: proofRollup2,
+ blocks: blocksRollup2,
+ parentStateRootHash: parentStateRootHashRollup2,
+ } = getProverTestData(PROOF_MODE, "rollup-2.json");
+
+ async function deployZkEvmFixture() {
+ const PlonkVerifierFactory = await ethers.getContractFactory("PlonkVerifierFull");
+ const plonkVerifier = await PlonkVerifierFactory.deploy();
+ await plonkVerifier.deployed();
+
+ verifier = plonkVerifier.address;
+
+ const multiRollupZkEvm = (await deployUpgradableFromFactory(
+ "TestZkEvmV2",
+ [
+ parentStateRootHashRollup1,
+ firstBlockNumberRollup1 - 1,
+ verifier,
+ securityCouncil.address,
+ [operator.address],
+ ONE_DAY_IN_SECONDS,
+ INITIAL_WITHDRAW_LIMIT,
+ ],
+ {
+ initializer: "initialize(bytes32,uint256,address,address,address[],uint256,uint256)",
+ unsafeAllow: ["constructor"],
+ },
+ )) as TestZkEvmV2;
+
+ const zkEvm = (await deployUpgradableFromFactory(
+ "TestZkEvmV2",
+ [
+ parentStateRootHash,
+ firstBlockNumber - 1,
+ verifier,
+ securityCouncil.address,
+ [operator.address],
+ ONE_DAY_IN_SECONDS,
+ INITIAL_WITHDRAW_LIMIT,
+ ],
+ {
+ initializer: "initialize(bytes32,uint256,address,address,address[],uint256,uint256)",
+ unsafeAllow: ["constructor"],
+ },
+ )) as TestZkEvmV2;
+
+ return { zkEvm, multiRollupZkEvm };
+ }
+
+ before(async () => {
+ [admin, securityCouncil, operator] = await ethers.getSigners();
+ });
+
+ beforeEach(async () => {
+ const contracts = await loadFixture(deployZkEvmFixture);
+ zkEvm = contracts.zkEvm;
+ multiRollupZkEvm = contracts.multiRollupZkEvm;
+ });
+
+ describe("When not paused", () => {
+ describe("Multiple in a row with full proof", () => {
+ it("Should fail when starting rootHash does not match last known block starting hash", async () => {
+ await expect(
+ multiRollupZkEvm
+ .connect(operator)
+ .finalizeBlocks(blocksRollup1, proof, 0, BAD_STARTING_HASH, { gasLimit: 10_000_000 }),
+ ).to.be.revertedWithCustomError(multiRollupZkEvm, "StartingRootHashDoesNotMatch");
+ });
+
+ it("Should finalize multiple rollups and change the current timestamp", async () => {
+ const { currentTimestampAfterSecondCall } = await finalizeMultipleBlocks();
+ expect(currentTimestampAfterSecondCall.toNumber()).to.equal(
+ blocksRollup2[blocksRollup2.length - 1].l2BlockTimestamp,
+ );
+ });
+
+ it("Should finalize multiple rollups and change the current block number", async () => {
+ const { initialCurrentBlockNumber, currentBlockNumberAfterSecondCall } = await finalizeMultipleBlocks();
+ expect(currentBlockNumberAfterSecondCall).to.equal(
+ initialCurrentBlockNumber.add(blocksRollup1.length).add(blocksRollup2.length),
+ );
+ });
+
+ it("Should finalize multiple rollups and set the root hashes for each block", async () => {
+ const { initialCurrentBlockNumber, currentBlockNumberAfterFirstCall } = await finalizeMultipleBlocks();
+
+ const lastStateRootHashForFirstCall = await multiRollupZkEvm.stateRootHashes(
+ initialCurrentBlockNumber.add(blocksRollup1.length),
+ );
+ expect(lastStateRootHashForFirstCall).to.equal(blocksRollup1[blocksRollup1.length - 1].blockRootHash);
+
+ const lastStateRootHashForSecondCall = await multiRollupZkEvm.stateRootHashes(
+ currentBlockNumberAfterFirstCall.add(blocksRollup2.length),
+ );
+ expect(lastStateRootHashForSecondCall).to.equal(blocksRollup2[blocksRollup2.length - 1].blockRootHash);
+ });
+
+ it("Should finalize multiple rollups and emit BlockFinalized", async () => {
+ const {
+ firstRollupTransaction,
+ secondRollupTransaction,
+ initialCurrentBlockNumber,
+ currentBlockNumberAfterFirstCall,
+ } = await finalizeMultipleBlocks();
+
+ let blockNumber = initialCurrentBlockNumber.toNumber();
+ let expectedEventData = blocksRollup1.map(({ blockRootHash }) => {
+ blockNumber++;
+ return { blockNumber, stateRootHash: blockRootHash };
+ });
+
+ const { events } = await firstRollupTransaction.wait();
+
+ expect(events).to.not.be.undefined;
+
+ if (events) {
+ const filteredEvents = events.filter((event) => event.event === "BlockFinalized");
+ expect(filteredEvents.length).to.be.equal(expectedEventData.length);
+
+ // check for topic with non empty data
+ for (let i = 0; i < filteredEvents.length; i++) {
+ expect(filteredEvents[i].args?.blockNumber).to.deep.equal(expectedEventData[i].blockNumber);
+ expect(filteredEvents[i].args?.stateRootHash).to.deep.equal(expectedEventData[i].stateRootHash);
+ }
+ }
+
+ blockNumber = currentBlockNumberAfterFirstCall.toNumber();
+ expectedEventData = blocksRollup2.map(({ blockRootHash }) => {
+ blockNumber++;
+ return { blockNumber, stateRootHash: blockRootHash };
+ });
+
+ const { events: eventsSet2 } = await secondRollupTransaction.wait();
+
+ expect(eventsSet2).to.not.be.undefined;
+
+ if (eventsSet2) {
+ const filteredEvents = eventsSet2.filter((event) => event.event === "BlockFinalized");
+ expect(filteredEvents.length).to.be.equal(expectedEventData.length);
+
+ // check for topic with non empty data
+ for (let i = 0; i < filteredEvents.length; i++) {
+ expect(filteredEvents[i].args?.blockNumber).to.deep.equal(expectedEventData[i].blockNumber);
+ expect(filteredEvents[i].args?.stateRootHash).to.deep.equal(expectedEventData[i].stateRootHash);
+ }
+ }
+ });
+
+ it("Should finalize multiple blocks and emit BlocksVerificationDone", async () => {
+ const {
+ firstRollupTransaction,
+ secondRollupTransaction,
+ currentBlockNumberAfterFirstCall,
+ currentBlockNumberAfterSecondCall,
+ } = await finalizeMultipleBlocks();
+
+ await expect(firstRollupTransaction)
+ .to.emit(multiRollupZkEvm, "BlocksVerificationDone")
+ // blocks are zero based, based on initial data and this should be 1 less
+ .withArgs(
+ currentBlockNumberAfterFirstCall,
+ parentStateRootHashRollup1,
+ blocksRollup1[blocksRollup1.length - 1].blockRootHash,
+ );
+
+ await expect(secondRollupTransaction)
+ .to.emit(multiRollupZkEvm, "BlocksVerificationDone")
+ // blocks are zero based, based on initial data and this should be 1 less
+ .withArgs(
+ currentBlockNumberAfterSecondCall,
+ parentStateRootHashRollup2,
+ blocksRollup2[blocksRollup2.length - 1].blockRootHash,
+ );
+ });
+ });
+
+ describe("finalizeBlocks with full proof", () => {
+ it("Should finalize blocks", async () => {
+ const txHashes: string[][] = [];
+ for (const tx of getTransactionsToBeDecoded(blocks)) {
+ const txs = await zkEvm.extractMessageHashes(tx);
+ txHashes.push(txs);
+ await zkEvm.addL1L2MessageHash(tx);
+ }
+
+ const tx = await zkEvm
+ .connect(operator)
+ .finalizeBlocks(blocks, proof, 0, parentStateRootHash, { gasLimit: 10_000_000 });
+ const { events } = await tx.wait();
+
+ expect(events).to.not.be.undefined;
+
+ if (events) {
+ const filteredEvents = events.filter((event) => event.event === "L1L2MessagesReceivedOnL2");
+ expect(filteredEvents.length).to.equal(txHashes.length);
+
+ for (let i = 0; i < filteredEvents.length; i++) {
+ expect(filteredEvents[i].args?.messageHashes).to.deep.equal(txHashes[i]);
+ }
+ }
+ });
+
+ it("Should finalize blocks and change currentL2BlockNumber", async () => {
+ for (const tx of getTransactionsToBeDecoded(blocks)) {
+ await zkEvm.addL1L2MessageHash(tx);
+ }
+
+ const currentBlockNumberKnownBeforeExecution = await zkEvm.currentL2BlockNumber();
+ expect(currentBlockNumberKnownBeforeExecution).to.equal(firstBlockNumber - 1);
+
+ await zkEvm.connect(operator).finalizeBlocks(blocks, proof, 0, parentStateRootHash, { gasLimit: 10_000_000 });
+
+ const currentBlockNumberKnownAfterExecution = await zkEvm.currentL2BlockNumber();
+ expect(currentBlockNumberKnownAfterExecution).to.equal(
+ currentBlockNumberKnownBeforeExecution.add(blocks.length),
+ );
+ });
+
+ it("Should finalize blocks and change currentTimestamp", async () => {
+ for (const tx of getTransactionsToBeDecoded(blocks)) {
+ await zkEvm.addL1L2MessageHash(tx);
+ }
+
+ let currentTimestamp = await zkEvm.currentTimestamp();
+ expect(currentTimestamp).to.equal(0);
+
+ await zkEvm.connect(operator).finalizeBlocks(blocks, proof, 0, parentStateRootHash, { gasLimit: 10_000_000 });
+
+ currentTimestamp = await zkEvm.currentTimestamp();
+ expect(currentTimestamp).to.equal(blocks[blocks.length - 1].l2BlockTimestamp);
+ });
+
+ it("Should finalize blocks and emit BlockFinalized", async () => {
+ for (const tx of getTransactionsToBeDecoded(blocks)) {
+ await zkEvm.addL1L2MessageHash(tx);
+ }
+
+ let blockNumber = firstBlockNumber - 1;
+ const expectedEventData = blocks.map(({ blockRootHash }) => {
+ blockNumber++;
+ return { blockNumber, stateRootHash: blockRootHash };
+ });
+
+ const tx = await zkEvm
+ .connect(operator)
+ .finalizeBlocks(blocks, proof, 0, parentStateRootHash, { gasLimit: 10_000_000 });
+
+ const { events } = await tx.wait();
+
+ expect(events).to.not.be.undefined;
+
+ if (events) {
+ const filteredEvents = events.filter((event) => event.event === "BlockFinalized");
+ expect(filteredEvents.length).to.be.equal(expectedEventData.length);
+
+ // check for topic with non empty data
+ for (let i = 0; i < filteredEvents.length; i++) {
+ expect(filteredEvents[i].args?.blockNumber).to.deep.equal(expectedEventData[i].blockNumber);
+ expect(filteredEvents[i].args?.stateRootHash).to.deep.equal(expectedEventData[i].stateRootHash);
+ }
+ }
+ });
+
+ it("Should finalize blocks and emit BlocksVerificationDone", async () => {
+ for (const tx of getTransactionsToBeDecoded(blocks)) {
+ await zkEvm.addL1L2MessageHash(tx);
+ }
+ const previousKnownStartingNumber = await zkEvm.currentL2BlockNumber();
+ await expect(
+ zkEvm.connect(operator).finalizeBlocks(blocks, proof, 0, parentStateRootHash, { gasLimit: 10_000_000 }),
+ )
+ .to.emit(zkEvm, "BlocksVerificationDone")
+ // blocks are zero based, based on initial data and this should be 1 less
+ .withArgs(
+ previousKnownStartingNumber.add(blocks.length),
+ parentStateRootHash,
+ blocks[blocks.length - 1].blockRootHash,
+ );
+ });
+
+ it("Should fail to process with future BlockTimeStamp", async () => {
+ const { blocks } = getProverTestData(PROOF_MODE, "output-file.json");
+
+ for (const tx of getTransactionsToBeDecoded(blocks)) {
+ await zkEvm.addL1L2MessageHash(tx);
+ }
+
+ blocks[blocks.length - 1].l2BlockTimestamp = 3123456789;
+ await expect(
+ zkEvm.connect(operator).finalizeBlocks(blocks, proof, 0, parentStateRootHash, { gasLimit: 10_000_000 }),
+ ).to.be.revertedWithCustomError(zkEvm, "BlockTimestampError");
+ });
+
+ it.skip("Should fail to process with duplicate data", async () => {
+ const { blocks } = getProverTestData(PROOF_MODE, "rollup-2.json");
+
+ for (const tx of getTransactionsToBeDecoded(blocks)) {
+ await zkEvm.addL1L2MessageHash(tx);
+ }
+
+ await expect(
+ zkEvm
+ .connect(operator)
+ .finalizeBlocks([...blocks, ...blocks], proof, 0, parentStateRootHash, { gasLimit: 15_000_000 }),
+ ).to.be.revertedWithCustomError(zkEvm, "MessageAlreadyReceived");
+ });
+
+ it.skip("Should fail when messages not marked as sent", async () => {
+ await expect(
+ zkEvm.connect(operator).finalizeBlocks(blocks, proof, 0, parentStateRootHash, { gasLimit: 10_000_000 }),
+ ).to.be.revertedWithCustomError(zkEvm, "L1L2MessageNotSent");
+ });
+
+ it("Should fail when proof does not match", async () => {
+ for (const tx of getTransactionsToBeDecoded(blocks)) {
+ await zkEvm.addL1L2MessageHash(tx);
+ }
+
+ const data = getProverTestData(PROOF_MODE, "output-file.json");
+ // remove a transaction breaking the hashes
+ data.blocks[0].transactions.push(data.blocks[0].transactions[0]);
+
+ await expect(
+ zkEvm.connect(operator).finalizeBlocks(data.blocks, proof, 0, parentStateRootHash, { gasLimit: 10_000_000 }),
+ ).to.be.revertedWithCustomError(zkEvm, "InvalidProof");
+ });
+
+ it("Should set the state hash for each block", async () => {
+ for (const tx of getTransactionsToBeDecoded(blocks)) {
+ await zkEvm.addL1L2MessageHash(tx);
+ }
+ const previousKnownStartingNumber = await zkEvm.currentL2BlockNumber();
+
+ await zkEvm.connect(operator).finalizeBlocks(blocks, proof, 0, parentStateRootHash, { gasLimit: 10_000_000 });
+
+ const blockHash = await zkEvm.stateRootHashes(previousKnownStartingNumber.add(blocks.length));
+ expect(blockHash).to.equal(blocks[blocks.length - 1].blockRootHash);
+ });
+
+ it("Should fail when starting rootHash does not match last known block starting hash", async () => {
+ await expect(
+ zkEvm.connect(operator).finalizeBlocks(blocks, proof, 0, BAD_STARTING_HASH, { gasLimit: 10_000_000 }),
+ ).to.be.revertedWithCustomError(zkEvm, "StartingRootHashDoesNotMatch");
+ });
+ });
+ });
+
+ async function finalizeMultipleBlocks(): Promise<{
+ firstRollupTransaction: ContractTransaction;
+ secondRollupTransaction: ContractTransaction;
+ initialCurrentBlockNumber: BigNumber;
+ currentBlockNumberAfterFirstCall: BigNumber;
+ currentBlockNumberAfterSecondCall: BigNumber;
+ initialCurrentTimestamp: BigNumber;
+ currentTimestampAfterFirstCall: BigNumber;
+ currentTimestampAfterSecondCall: BigNumber;
+ }> {
+ const txHashes: string[][] = [];
+
+ for (const tx of getTransactionsToBeDecoded(blocksRollup1)) {
+ const txs = await multiRollupZkEvm.extractMessageHashes(tx);
+ txHashes.push(txs);
+ await multiRollupZkEvm.addL1L2MessageHash(tx);
+ }
+
+ const initialCurrentBlockNumber = await multiRollupZkEvm.currentL2BlockNumber();
+ expect(initialCurrentBlockNumber).to.equal(firstBlockNumberRollup1 - 1);
+
+ const initialCurrentTimestamp = await multiRollupZkEvm.currentTimestamp();
+ expect(initialCurrentTimestamp).to.equal(0);
+
+ const firstRollupTransaction = await multiRollupZkEvm
+ .connect(operator)
+ .finalizeBlocks(blocksRollup1, proofRollup1, 0, parentStateRootHashRollup1, { gasLimit: 10_000_000 });
+
+ const currentBlockNumberAfterFirstCall = await multiRollupZkEvm.currentL2BlockNumber();
+ expect(currentBlockNumberAfterFirstCall).to.equal(initialCurrentBlockNumber.add(blocksRollup1.length));
+
+ const currentTimestampAfterFirstCall = await multiRollupZkEvm.currentTimestamp();
+ expect(currentTimestampAfterFirstCall).to.equal(blocksRollup1[blocksRollup1.length - 1].l2BlockTimestamp);
+
+ for (const tx of getTransactionsToBeDecoded(blocksRollup2)) {
+ const txs = await multiRollupZkEvm.extractMessageHashes(tx);
+ txHashes.push(txs);
+ await multiRollupZkEvm.addL1L2MessageHash(tx);
+ }
+
+ const secondRollupTransaction = await multiRollupZkEvm
+ .connect(operator)
+ .finalizeBlocks(blocksRollup2, proofRollup2, 0, parentStateRootHashRollup2, { gasLimit: 10_000_000 });
+
+ const currentBlockNumberAfterSecondCall = await multiRollupZkEvm.currentL2BlockNumber();
+ expect(currentBlockNumberAfterSecondCall).to.equal(currentBlockNumberAfterFirstCall.add(blocksRollup2.length));
+
+ const currentTimestampAfterSecondCall = await multiRollupZkEvm.currentTimestamp();
+ expect(currentTimestampAfterSecondCall).to.equal(blocksRollup2[blocksRollup2.length - 1].l2BlockTimestamp);
+
+ return {
+ firstRollupTransaction,
+ secondRollupTransaction,
+ initialCurrentBlockNumber,
+ currentBlockNumberAfterFirstCall,
+ currentBlockNumberAfterSecondCall,
+ initialCurrentTimestamp,
+ currentTimestampAfterFirstCall,
+ currentTimestampAfterSecondCall,
+ };
+ }
+});
diff --git a/contracts/test/ZkEvmV2_FullLarge.ts b/contracts/test/ZkEvmV2_FullLarge.ts
new file mode 100644
index 000000000..8d97919e8
--- /dev/null
+++ b/contracts/test/ZkEvmV2_FullLarge.ts
@@ -0,0 +1,437 @@
+import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
+import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
+import { expect } from "chai";
+import { BigNumber, ContractTransaction } from "ethers";
+import { ethers } from "hardhat";
+import { TestZkEvmV2 } from "../typechain-types";
+import { BAD_STARTING_HASH, INITIAL_WITHDRAW_LIMIT, ONE_DAY_IN_SECONDS } from "./utils/constants";
+import { deployUpgradableFromFactory } from "./utils/deployment";
+import { getProverTestData, getTransactionsToBeDecoded } from "./utils/helpers";
+
+describe("ZK EVM V2 contract with full large verifier", () => {
+ let zkEvm: TestZkEvmV2;
+ let multiRollupZkEvm: TestZkEvmV2;
+
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ let admin: SignerWithAddress;
+ let verifier: string;
+ let securityCouncil: SignerWithAddress;
+ let operator: SignerWithAddress;
+
+ const PROOF_MODE = "FullLarge";
+
+ const { proof, blocks, parentStateRootHash, firstBlockNumber } = getProverTestData(PROOF_MODE, "output-file.json");
+ const {
+ proof: proofRollup1,
+ blocks: blocksRollup1,
+ parentStateRootHash: parentStateRootHashRollup1,
+ firstBlockNumber: firstBlockNumberRollup1,
+ } = getProverTestData(PROOF_MODE, "rollup-1.json");
+ const {
+ proof: proofRollup2,
+ blocks: blocksRollup2,
+ parentStateRootHash: parentStateRootHashRollup2,
+ } = getProverTestData(PROOF_MODE, "rollup-2.json");
+
+ async function deployZkEvmFixture() {
+ const PlonkVerifierFactory = await ethers.getContractFactory("PlonkVerifierFullLarge");
+ const plonkVerifier = await PlonkVerifierFactory.deploy();
+ await plonkVerifier.deployed();
+
+ verifier = plonkVerifier.address;
+
+ const multiRollupZkEvm = (await deployUpgradableFromFactory(
+ "TestZkEvmV2",
+ [
+ parentStateRootHashRollup1,
+ firstBlockNumberRollup1 - 1,
+ verifier,
+ securityCouncil.address,
+ [operator.address],
+ ONE_DAY_IN_SECONDS,
+ INITIAL_WITHDRAW_LIMIT,
+ ],
+ {
+ initializer: "initialize(bytes32,uint256,address,address,address[],uint256,uint256)",
+ unsafeAllow: ["constructor"],
+ },
+ )) as TestZkEvmV2;
+
+ const zkEvm = (await deployUpgradableFromFactory(
+ "TestZkEvmV2",
+ [
+ parentStateRootHash,
+ firstBlockNumber - 1,
+ verifier,
+ securityCouncil.address,
+ [operator.address],
+ ONE_DAY_IN_SECONDS,
+ INITIAL_WITHDRAW_LIMIT,
+ ],
+ {
+ initializer: "initialize(bytes32,uint256,address,address,address[],uint256,uint256)",
+ unsafeAllow: ["constructor"],
+ },
+ )) as TestZkEvmV2;
+
+ return { zkEvm, multiRollupZkEvm };
+ }
+
+ before(async () => {
+ [admin, securityCouncil, operator] = await ethers.getSigners();
+ });
+
+ beforeEach(async () => {
+ const contracts = await loadFixture(deployZkEvmFixture);
+ zkEvm = contracts.zkEvm;
+ multiRollupZkEvm = contracts.multiRollupZkEvm;
+ });
+
+ describe("When not paused", () => {
+ describe("Multiple in a row with full proof", () => {
+ it("Should fail when starting rootHash does not match last known block starting hash", async () => {
+ await expect(
+ multiRollupZkEvm
+ .connect(operator)
+ .finalizeBlocks(blocksRollup1, proof, 0, BAD_STARTING_HASH, { gasLimit: 10_000_000 }),
+ ).to.be.revertedWithCustomError(multiRollupZkEvm, "StartingRootHashDoesNotMatch");
+ });
+
+ it("Should finalize multiple rollups and change the current timestamp", async () => {
+ const { currentTimestampAfterSecondCall } = await finalizeMultipleBlocks();
+ expect(currentTimestampAfterSecondCall.toNumber()).to.equal(
+ blocksRollup2[blocksRollup2.length - 1].l2BlockTimestamp,
+ );
+ });
+
+ it("Should finalize multiple rollups and change the current block number", async () => {
+ const { initialCurrentBlockNumber, currentBlockNumberAfterSecondCall } = await finalizeMultipleBlocks();
+ expect(currentBlockNumberAfterSecondCall).to.equal(
+ initialCurrentBlockNumber.add(blocksRollup1.length).add(blocksRollup2.length),
+ );
+ });
+
+ it("Should finalize multiple rollups and set the root hashes for each block", async () => {
+ const { initialCurrentBlockNumber, currentBlockNumberAfterFirstCall } = await finalizeMultipleBlocks();
+
+ const lastStateRootHashForFirstCall = await multiRollupZkEvm.stateRootHashes(
+ initialCurrentBlockNumber.add(blocksRollup1.length),
+ );
+ expect(lastStateRootHashForFirstCall).to.equal(blocksRollup1[blocksRollup1.length - 1].blockRootHash);
+
+ const lastStateRootHashForSecondCall = await multiRollupZkEvm.stateRootHashes(
+ currentBlockNumberAfterFirstCall.add(blocksRollup2.length),
+ );
+ expect(lastStateRootHashForSecondCall).to.equal(blocksRollup2[blocksRollup2.length - 1].blockRootHash);
+ });
+
+ it("Should finalize multiple rollups and emit BlockFinalized", async () => {
+ const {
+ firstRollupTransaction,
+ secondRollupTransaction,
+ initialCurrentBlockNumber,
+ currentBlockNumberAfterFirstCall,
+ } = await finalizeMultipleBlocks();
+
+ let blockNumber = initialCurrentBlockNumber.toNumber();
+ let expectedEventData = blocksRollup1.map(({ blockRootHash }) => {
+ blockNumber++;
+ return { blockNumber, stateRootHash: blockRootHash };
+ });
+
+ const { events } = await firstRollupTransaction.wait();
+
+ expect(events).to.not.be.undefined;
+
+ if (events) {
+ const filteredEvents = events.filter((event) => event.event === "BlockFinalized");
+ expect(filteredEvents.length).to.be.equal(expectedEventData.length);
+
+ // check for topic with non empty data
+ for (let i = 0; i < filteredEvents.length; i++) {
+ expect(filteredEvents[i].args?.blockNumber).to.deep.equal(expectedEventData[i].blockNumber);
+ expect(filteredEvents[i].args?.stateRootHash).to.deep.equal(expectedEventData[i].stateRootHash);
+ }
+ }
+
+ blockNumber = currentBlockNumberAfterFirstCall.toNumber();
+ expectedEventData = blocksRollup2.map(({ blockRootHash }) => {
+ blockNumber++;
+ return { blockNumber, stateRootHash: blockRootHash };
+ });
+
+ const { events: eventsSet2 } = await secondRollupTransaction.wait();
+
+ expect(eventsSet2).to.not.be.undefined;
+
+ if (eventsSet2) {
+ const filteredEvents = eventsSet2.filter((event) => event.event === "BlockFinalized");
+ expect(filteredEvents.length).to.be.equal(expectedEventData.length);
+
+ // check for topic with non empty data
+ for (let i = 0; i < filteredEvents.length; i++) {
+ expect(filteredEvents[i].args?.blockNumber).to.deep.equal(expectedEventData[i].blockNumber);
+ expect(filteredEvents[i].args?.stateRootHash).to.deep.equal(expectedEventData[i].stateRootHash);
+ }
+ }
+ });
+
+ it("Should finalize multiple blocks and emit BlocksVerificationDone", async () => {
+ const {
+ firstRollupTransaction,
+ secondRollupTransaction,
+ currentBlockNumberAfterFirstCall,
+ currentBlockNumberAfterSecondCall,
+ } = await finalizeMultipleBlocks();
+
+ await expect(firstRollupTransaction)
+ .to.emit(multiRollupZkEvm, "BlocksVerificationDone")
+ // blocks are zero based, based on initial data and this should be 1 less
+ .withArgs(
+ currentBlockNumberAfterFirstCall,
+ parentStateRootHashRollup1,
+ blocksRollup1[blocksRollup1.length - 1].blockRootHash,
+ );
+
+ await expect(secondRollupTransaction)
+ .to.emit(multiRollupZkEvm, "BlocksVerificationDone")
+ // blocks are zero based, based on initial data and this should be 1 less
+ .withArgs(
+ currentBlockNumberAfterSecondCall,
+ parentStateRootHashRollup2,
+ blocksRollup2[blocksRollup2.length - 1].blockRootHash,
+ );
+ });
+ });
+
+ describe("finalizeBlocks with full proof", () => {
+ it("Should finalize blocks", async () => {
+ const txHashes: string[][] = [];
+ for (const tx of getTransactionsToBeDecoded(blocks)) {
+ const txs = await zkEvm.extractMessageHashes(tx);
+ txHashes.push(txs);
+ await zkEvm.addL1L2MessageHash(tx);
+ }
+
+ const tx = await zkEvm
+ .connect(operator)
+ .finalizeBlocks(blocks, proof, 0, parentStateRootHash, { gasLimit: 10_000_000 });
+ const { events } = await tx.wait();
+
+ expect(events).to.not.be.undefined;
+
+ if (events) {
+ const filteredEvents = events.filter((event) => event.event === "L1L2MessagesReceivedOnL2");
+ expect(filteredEvents.length).to.equal(txHashes.length);
+
+ for (let i = 0; i < filteredEvents.length; i++) {
+ expect(filteredEvents[i].args?.messageHashes).to.deep.equal(txHashes[i]);
+ }
+ }
+ });
+
+ it("Should finalize blocks and change currentL2BlockNumber", async () => {
+ for (const tx of getTransactionsToBeDecoded(blocks)) {
+ await zkEvm.addL1L2MessageHash(tx);
+ }
+
+ const currentBlockNumberKnownBeforeExecution = await zkEvm.currentL2BlockNumber();
+ expect(currentBlockNumberKnownBeforeExecution).to.equal(firstBlockNumber - 1);
+
+ await zkEvm.connect(operator).finalizeBlocks(blocks, proof, 0, parentStateRootHash, { gasLimit: 10_000_000 });
+
+ const currentBlockNumberKnownAfterExecution = await zkEvm.currentL2BlockNumber();
+ expect(currentBlockNumberKnownAfterExecution).to.equal(
+ currentBlockNumberKnownBeforeExecution.add(blocks.length),
+ );
+ });
+
+ it("Should finalize blocks and change currentTimestamp", async () => {
+ for (const tx of getTransactionsToBeDecoded(blocks)) {
+ await zkEvm.addL1L2MessageHash(tx);
+ }
+
+ let currentTimestamp = await zkEvm.currentTimestamp();
+ expect(currentTimestamp).to.equal(0);
+
+ await zkEvm.connect(operator).finalizeBlocks(blocks, proof, 0, parentStateRootHash, { gasLimit: 10_000_000 });
+
+ currentTimestamp = await zkEvm.currentTimestamp();
+ expect(currentTimestamp).to.equal(blocks[blocks.length - 1].l2BlockTimestamp);
+ });
+
+ it("Should finalize blocks and emit BlockFinalized", async () => {
+ for (const tx of getTransactionsToBeDecoded(blocks)) {
+ await zkEvm.addL1L2MessageHash(tx);
+ }
+
+ let blockNumber = firstBlockNumber - 1;
+ const expectedEventData = blocks.map(({ blockRootHash }) => {
+ blockNumber++;
+ return { blockNumber, stateRootHash: blockRootHash };
+ });
+
+ const tx = await zkEvm
+ .connect(operator)
+ .finalizeBlocks(blocks, proof, 0, parentStateRootHash, { gasLimit: 10_000_000 });
+
+ const { events } = await tx.wait();
+
+ expect(events).to.not.be.undefined;
+
+ if (events) {
+ const filteredEvents = events.filter((event) => event.event === "BlockFinalized");
+ expect(filteredEvents.length).to.be.equal(expectedEventData.length);
+
+ // check for topic with non empty data
+ for (let i = 0; i < filteredEvents.length; i++) {
+ expect(filteredEvents[i].args?.blockNumber).to.deep.equal(expectedEventData[i].blockNumber);
+ expect(filteredEvents[i].args?.stateRootHash).to.deep.equal(expectedEventData[i].stateRootHash);
+ }
+ }
+ });
+
+ it("Should finalize blocks and emit BlocksVerificationDone", async () => {
+ for (const tx of getTransactionsToBeDecoded(blocks)) {
+ await zkEvm.addL1L2MessageHash(tx);
+ }
+ const previousKnownStartingNumber = await zkEvm.currentL2BlockNumber();
+ await expect(
+ zkEvm.connect(operator).finalizeBlocks(blocks, proof, 0, parentStateRootHash, { gasLimit: 10_000_000 }),
+ )
+ .to.emit(zkEvm, "BlocksVerificationDone")
+ // blocks are zero based, based on initial data and this should be 1 less
+ .withArgs(
+ previousKnownStartingNumber.add(blocks.length),
+ parentStateRootHash,
+ blocks[blocks.length - 1].blockRootHash,
+ );
+ });
+
+ it("Should fail to process with future BlockTimeStamp", async () => {
+ const { blocks } = getProverTestData(PROOF_MODE, "output-file.json");
+
+ for (const tx of getTransactionsToBeDecoded(blocks)) {
+ await zkEvm.addL1L2MessageHash(tx);
+ }
+
+ blocks[blocks.length - 1].l2BlockTimestamp = 3123456789;
+ await expect(
+ zkEvm.connect(operator).finalizeBlocks(blocks, proof, 0, parentStateRootHash, { gasLimit: 10_000_000 }),
+ ).to.be.revertedWithCustomError(zkEvm, "BlockTimestampError");
+ });
+
+ it.skip("Should fail to process with duplicate data", async () => {
+ for (const tx of getTransactionsToBeDecoded(blocks)) {
+ await zkEvm.addL1L2MessageHash(tx);
+ }
+
+ await expect(
+ zkEvm
+ .connect(operator)
+ .finalizeBlocks([...blocks, ...blocks], proof, 0, parentStateRootHash, { gasLimit: 15_000_000 }),
+ ).to.be.revertedWithCustomError(zkEvm, "MessageAlreadyReceived");
+ });
+
+ it.skip("Should fail when messages not marked as sent", async () => {
+ await expect(
+ zkEvm.connect(operator).finalizeBlocks(blocks, proof, 0, parentStateRootHash, { gasLimit: 10_000_000 }),
+ ).to.be.revertedWithCustomError(zkEvm, "L1L2MessageNotSent");
+ });
+
+ it("Should fail when proof does not match", async () => {
+ for (const tx of getTransactionsToBeDecoded(blocks)) {
+ await zkEvm.addL1L2MessageHash(tx);
+ }
+
+ const data = getProverTestData(PROOF_MODE, "output-file.json");
+ // remove a transaction breaking the hashes
+ data.blocks[0].transactions.pop();
+
+ await expect(
+ zkEvm.connect(operator).finalizeBlocks(data.blocks, proof, 0, parentStateRootHash, { gasLimit: 10_000_000 }),
+ ).to.be.revertedWithCustomError(zkEvm, "InvalidProof");
+ });
+
+ it("Should set the state hash for each block", async () => {
+ for (const tx of getTransactionsToBeDecoded(blocks)) {
+ await zkEvm.addL1L2MessageHash(tx);
+ }
+ const previousKnownStartingNumber = await zkEvm.currentL2BlockNumber();
+
+ await zkEvm.connect(operator).finalizeBlocks(blocks, proof, 0, parentStateRootHash, { gasLimit: 10_000_000 });
+
+ const blockHash = await zkEvm.stateRootHashes(previousKnownStartingNumber.add(blocks.length));
+ expect(blockHash).to.equal(blocks[blocks.length - 1].blockRootHash);
+ });
+
+ it("Should fail when starting rootHash does not match last known block starting hash", async () => {
+ await expect(
+ zkEvm.connect(operator).finalizeBlocks(blocks, proof, 0, BAD_STARTING_HASH, { gasLimit: 10_000_000 }),
+ ).to.be.revertedWithCustomError(zkEvm, "StartingRootHashDoesNotMatch");
+ });
+ });
+ });
+
+ async function finalizeMultipleBlocks(): Promise<{
+ firstRollupTransaction: ContractTransaction;
+ secondRollupTransaction: ContractTransaction;
+ initialCurrentBlockNumber: BigNumber;
+ currentBlockNumberAfterFirstCall: BigNumber;
+ currentBlockNumberAfterSecondCall: BigNumber;
+ initialCurrentTimestamp: BigNumber;
+ currentTimestampAfterFirstCall: BigNumber;
+ currentTimestampAfterSecondCall: BigNumber;
+ }> {
+ const txHashes: string[][] = [];
+
+ for (const tx of getTransactionsToBeDecoded(blocksRollup1)) {
+ const txs = await multiRollupZkEvm.extractMessageHashes(tx);
+ txHashes.push(txs);
+ await multiRollupZkEvm.addL1L2MessageHash(tx);
+ }
+
+ const initialCurrentBlockNumber = await multiRollupZkEvm.currentL2BlockNumber();
+ expect(initialCurrentBlockNumber).to.equal(firstBlockNumberRollup1 - 1);
+
+ const initialCurrentTimestamp = await multiRollupZkEvm.currentTimestamp();
+ expect(initialCurrentTimestamp).to.equal(0);
+
+ const firstRollupTransaction = await multiRollupZkEvm
+ .connect(operator)
+ .finalizeBlocks(blocksRollup1, proofRollup1, 0, parentStateRootHashRollup1, { gasLimit: 10_000_000 });
+
+ const currentBlockNumberAfterFirstCall = await multiRollupZkEvm.currentL2BlockNumber();
+ expect(currentBlockNumberAfterFirstCall).to.equal(initialCurrentBlockNumber.add(blocksRollup1.length));
+
+ const currentTimestampAfterFirstCall = await multiRollupZkEvm.currentTimestamp();
+ expect(currentTimestampAfterFirstCall).to.equal(blocksRollup1[blocksRollup1.length - 1].l2BlockTimestamp);
+
+ for (const tx of getTransactionsToBeDecoded(blocksRollup2)) {
+ const txs = await multiRollupZkEvm.extractMessageHashes(tx);
+ txHashes.push(txs);
+ await multiRollupZkEvm.addL1L2MessageHash(tx);
+ }
+
+ const secondRollupTransaction = await multiRollupZkEvm
+ .connect(operator)
+ .finalizeBlocks(blocksRollup2, proofRollup2, 0, parentStateRootHashRollup2, { gasLimit: 10_000_000 });
+
+ const currentBlockNumberAfterSecondCall = await multiRollupZkEvm.currentL2BlockNumber();
+ expect(currentBlockNumberAfterSecondCall).to.equal(currentBlockNumberAfterFirstCall.add(blocksRollup2.length));
+
+ const currentTimestampAfterSecondCall = await multiRollupZkEvm.currentTimestamp();
+ expect(currentTimestampAfterSecondCall).to.equal(blocksRollup2[blocksRollup2.length - 1].l2BlockTimestamp);
+
+ return {
+ firstRollupTransaction,
+ secondRollupTransaction,
+ initialCurrentBlockNumber,
+ currentBlockNumberAfterFirstCall,
+ currentBlockNumberAfterSecondCall,
+ initialCurrentTimestamp,
+ currentTimestampAfterFirstCall,
+ currentTimestampAfterSecondCall,
+ };
+ }
+});
diff --git a/contracts/test/testData/Full/output-file.json b/contracts/test/testData/Full/output-file.json
new file mode 100644
index 000000000..dd713d9b4
--- /dev/null
+++ b/contracts/test/testData/Full/output-file.json
@@ -0,0 +1,308 @@
+{
+ "proof": "0x23821aca8b8a72a2a672434960684640782865bde1d2a6e9f238a2b9cf4c16e203fd6a2b35336aaabc373367d14ec67e82460e9eb2008699f7e055f43b3718132179526db856e99b57ab0d38e1d69439e399ac5bbdf766c36c756575f8dc34c618f3606f7f79487c8bc55a1817eb4b04c679d241b9f98b86ab6364fe6d00318e08017a7a933de03ee950f3fbe5840004d2ce7f9c48d667ecebaad9cd4cc4f47f14924655d581db624b02f3fa1b2022c53c23dfff612eaa4458a38f23bb5a58da125dbaa7b4053189f3c800c28641288b0a7c75d04591c99a1b0666b27db7518d18ff1d42273551e0edc1d73369aa420e83ee905ba1446022c7a75b82973497222a111728c313484f6a2f726950af07850216a09b912c94085d2da660646c5f4908b8927c36ba13dc60ae28d071e9c604fadb66dbaf4979cc447e074c328f278e1cfc649e531783b23c2f469d1e90d58888fe9bb3b65b806f2632eb5aca2f43e716d734fdb03ba8867997c478c5cb72418643a1bb4eeae181c4692dc77b01882f007adb191885268d29282ba206121ba54ec5ff1281195b1aa3894e2bfa3ca084173105c58f474d672ad59b39e1e601e9651627a778417a6f1dcfe7c8342211f221361a8ed2f748f7788e749dfa67d7131af40f2d5fbb4a35c01067b59c82e0740bc73c321863e4cb34122de56506d1b90baa3227776b92f788edb61f0d828a2317db3fd6ad6a6bd30425a86c0c4f80b64b21fd695511bc612f8bdc479adfa92314f52136bde78a5adec621ad9b61ec7920e18de55d068989259fefa8d55dc97d1e37120849dbbc4440b81e335b897e977349cf0e055ce36495132be24bfe45da1db587822115120fc15572ce3e0723e1d90abc940a873bb83c4d64a481f331d8075f452867978605a93b282ad4e5a94b339e5203070d192ab7dac31312048cba13c44edfea3fc4ec9b958d855b6a3a494f1c5b1832678fce81856aed4265b27627c2d582bd3c8ce87212cefb03893db38388030fca44cdbb4d43c98c2d731633241ff322819cb59e9b36c09eeb9a8f043c76db4a7b924a288c137d6b8ce2a0592c69548a780e32eff92812c87b1ea92777050064fdd83ca7f832d61ccec422002567d8e41b950cd07c211ba711f1022a799f4277af8bb4ddff625d5cacc6c00210540550aa0aaf3a4bf4d27d528814d0673b6a9cf0614562a5cb020a2aeb8b281dcccfb2a3c8a85652cb4cd96db617c001edf3fde0b79489ac8712fbefa7c7f8214459c97cdddc6ef1ea85cb7bc587c309983fa4c7b8c5dc410ee3a27f1c4d6b",
+ "proverMode": "full",
+ "verifierIndex": 1,
+ "blocksData": [
+ {
+ "rlpEncodedTransactions": [
+ "0x02f9091382e70882054484962fc6ee85174876e800832625a094508ca82df566dcd1b0de8296e70a96332cd644ec80b908e4f4b476e100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000045161d5e6ce5e92df9502203fd8e60b26cb70894d99155576d7776057cf189577b3f762c86d9b7dcb0ccf3be280ac541f2d0514fcea664dadd7456cf50eaf2d2f10013acd8b6ab926aebce6479e1e69cba954d9a12280333a01764665a5753f2e465feaf9735a1a209af27b3961e604f09a79ed9ddc8cba4fd3f32446a2018bdbc0f876fee16158abb8ee62791d73979460eae03f4463c687662eb4ed55366f286fef0cc22ced3e884f6ada00b47ab4a643bbac88289e65a41ee741307eda80be8e8964030c3a8d5d5c81f99999a0b6d68a7b3af3a9a077c1d1cb7a61f0d1199089baf45c5dd81d359e4744b02f811ff6826d0db21c23686440c3c4019023691a394a7655f567ed5441ff236f1bd06db8fe26c9ceada60fd7f4b2180ad0db7833092bf2c52a1d0b4fe82a78bded7ee559ffb37f74de252e169ef907faa79cc84e888b947aad4534065024a5c3932e1ffa287fda0890fae04b45c2913c31fd992322973f916e0feaeee2180645f59b3336a07d7fdb2db52fdde27ccc544e2b030efa3db5e99efaab00e74314333b92bf5c358a61fd7e4ff920e8cf903866615adeaf2df544df269404bba4d4f94e87fc0f33d980511b4f3677eb337784bfb50209ae5f9fc6642c89a99698aec9767a936745e5538815221121ff20e1b6c89deeb292ea4eb17dbdd045924d3afcd4c418cf4f6a0a10c7231bbf852f2f4e18393f13cef6772e7e57ba3c74a3df64ae759e47be96b1185898570ce963d9c67acc30cfcb5ee5930478f657324e1427c76e49afd25111e0c9eec6842334ed75d0c5ba1c465342c6e75e0c5439f2fc0ea9761fe2b6890247f549ca4167425a953e642c31c9159b2d892edbee614e439766a7ddd8a66cd25bf9a45d2d1f580cf91c77386c1df74a4d98d38af559dc53177503e9157c6df8c8889d7f47bcee62749efb5b0614cce452e428e4780faad5ab385f77ecb89686c1c026927a9607bcd368c1212b0ee1dae5b27bd4796eb07b7cbb65accda47b8fc2336d53611ba5a880e3a84b41a65a9ff6e91c0f8ee8e00b506e81a8eacefc803d9474edbe0380898f324d49ea68fa2c7b2f5d52ee5e47308a0b2788937bb1195f5603845b07eeaad81b6d65f31028e614b18f73872399f6b643c8e33eb88eabdce099ff3eacd42193ea732a22826c6e79c13acb66deed9ca11ce95ffcd356a25e4f3e4b60f7fa36cd40d7171589fe695da70d6c976a4d79a84137713bd63eed6e5622f351faee3169cbb59c327d02471a720e7648c4c47a6592cc5d7c148fefbfeafd91d8ad350e14c691680e8fd47baf17f82b0aab2c3dbd23c76434c7b4e07c1e1c21a98886e2c6e866b8a60af2c75df6e29e0b40000070c6710f29e1b4e0c2e18cb2cf68344c8e259ee85408585f12b4ab7316f36b3c31fd1097faa1a036b6f1a74015420b3eca68daa03002ab06d810efce532fcd0996960288a1be9a2a43ac828b3ab4538853860a9bfebd8b7c0f65cdc54138137aed477733cc9726b748679d13f516150ccf39410795f32517bfbec0f3ed4dcfe2ef70029e35cacba809bbbc2a6c82543b19c1c1bff7fab52327ac00465b35015d9baaaec5a5aba682f9bef177e216e9e77319aadf64d7790878c01cef76b23eba1ed55f16ecfea6decde45118d2efbe4c22fb630d1c27742f4723148a934c1a97454fbf3fd603ef5a3f7d38ce4ae87bad4c25523afd669a112750bfaebf85f2e465cfbfb3bf8bad1b558ea91c556145eb87ec02f984d8c2f44369c14bf265536a5f32d36711add8c3ae841ce268b0c52533c64e3d841c9d4b63658f8e0ad32bb512aa7fb77f3baf72ff71bfe214676fa5650eae2971d0ce43b49abf645430874e5fc9dccf668217040fdf3cad9ab0140915c65fd5e9816743a249984e91746c5f0670d7fad7d642158142d570a7ec9e761f37bbc7ea7245befdeda160dc55ef1e2512a16ef5a3668ad785acf4fa836902aeb07ae03ceb24267beda32f3cfb7f5a9929434a3a9f6ddf7e4cfaecf5b071578c047d52a857dd5b30077d6a874892558f8798ee1c335aec8c8e47918ebec1f2a6c1cedc7b4d8185fc041defbef038d3b379ad7e84a047c132a49d0a28e86adf4d11d50da3a8875e89291558805f75e43c02645f08c8444184b87ab93d644b0051e7a6a756a059a97e91a82349d8907a33c1c90daa6e66deefb448b2cde8fe121bfc8b582c556527a7bfb11fde9b4bc645ad35369d630fe3593e020630231687d04cc5c81317eee0976e63f63644f20dd993203ec0ebf3be9af1b3a2d9ee3a779202d69d3d27b7aed886479604731caea8e3c8d878f030acb22a92fde9856ca342222759a9ad734e4a280c23573a003230f1c515e66c531beb518c2c4786d64c46c49854c2fd17722e1a82c08b3a35bc59f7771f29c4bf4e6a18ddebb66beac48ad7bda9122be176ded174b963bb4f3b8b13efdb9bef4608b9f3fdec2222b683dfa6bc41f247f76b2644dfe71e95f5b0b46aee8350e95a3afb46262a20d0e99e9216a317a571a319018d134ef83c9e335af1e46d0eae1377320c8036394d7218341eb4c775a97c2ca8817d1d0febd1e48604e33b395a13a9a36f1151fab3b1470bcfd924e6442ddc8cb7284bb159abef1d2ce5f23c3d892eb74bd31f02a8754aaf9d07476724c70349a8fd5d8d370579461052a9aae0072e79b5fb5a8b5c0f0ee747747554bb1608f7670b9e14b680ce360caa6097ccee08710816b325ac9d18038eab16b16de7a4d727f9dedb9a145a39952e31b264c136bd46d8b45f8683a2af9f27006325ad9e342bafb6ac4756485a79d8cf2d63d72a2284d1118bd195098891bc23831b605b47a9c8665d9bb7b7eb7b5966bc37a56c53f6fcbe28953add2b9fb7d6bf7d9d480563e0cd07f05226c9c8a3c3496cebed0000aced1c27ad9e3494362cd6da3d8686dde841fcc724d830e71160c0359393d089ae3e49f412b567823e5efdb4f43d246e35b2e73620a8b0d6c3f5f45709180c6ebeb48211c422f65a5374b5914b0d3fa1ca03f1960c7a50b6ddba710de8859c132e48e57c57485d937c11c4648fcb2b8a636f3b2a1e27ced0e081c57e8e3f50b5c78481fe743daafd2d15cdfc0",
+ "0x02f582e708823ed185012a05f200852e90edd0008252089452578e1fe11f92618dd178629156de2307b1065d880234e1a85749806180c0",
+ "0x02f86e82e70813846ba55515846ba55515827223947d43aabc515c356145049227cee54b608342c0ad80b844095ea7b300000000000000000000000080e38291e06339d10aab483c65695d004dbd5c690000000000000000000000000000000000000000000000009275143d32c82d5dc0",
+ "0x02f84d82e70808846ba55515846ba55515828cc794e5d7c2a44ffddf6b295a15c148167daaaf5cf34f80a42e1a7d4d00000000000000000000000000000000000000000000000001866c3674ca28e7c0",
+ "0x02f9029682e70801846ba55515846ba55515830282209480e38291e06339d10aab483c65695d004dbd5c69861b48eb57e000b902642cc4081e000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000fcb9d92cf386180000000000000000000000000000000000000000000000000000000064bc7dcb000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001b48eb57e0000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000046ec4bb184528c3aee6f1419e11b28a97f33d4830000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f000000000000000000000000126824925a67f98dca1eb9d92d78d51c99f3a42700000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f901d082e70806846548b1a4846548b1ae8307c07494438670d41d5118003b2f42cc0466fbadd760dbf480b901a4ea5406320000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f000000000000000000000000000000000000000000000000000000000000012cfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed928fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed9dcfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed8ecfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed9a000000000000000000000000000000000000000000000000093a7cafa8b08e3b30000000000000000000000000000000000000000000000000011c37937e0800000000000000000000000000000000000000000000000000046823c1fd558d2f60000000000000000000000000000000000000000000000000007351f791fb3be000000000000000000000000a8a71d5fb2d5fbc8f31799c5cdd8ae67722885a50000000000000000000000000000000000000000000000000000000064bc5393c0",
+ "0x02f86f82e70809846548b1a4846548b1ae8301042f947d43aabc515c356145049227cee54b608342c0ad80b844095ea7b3000000000000000000000000438670d41d5118003b2f42cc0466fbadd760dbf40000000000000000000000000000000000000000000000047049d62f86d62000c0",
+ "0x02f8cf82e7080b846548b1a4846548b1ae8303374994b29caa2cb1feb7f4ccaa9dd9b8ad2022eaca6ec380b8a46170b162000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000413f019ead393d9d0c66745b08e818fe13179bd3da2d6559b2bb9c40aca9618c364ecece8c8fc0d89d0e5897210ae3b5521f49cbb178e00ee568be62d583edceb51b00000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013082e7080e846548b1a4846548b1ae8302cdc594c66149996d0263c0b42d3bc05e50db88658106ce80b9010418cbafe500000000000000000000000000000000000000000000000098a7d9b8314c0000000000000000000000000000000000000000000000000000001420824645e5d100000000000000000000000000000000000000000000000000000000000000a000000000000000000000000078ea6f35af55843f9d1d654f4a076ef435fa34690000000000000000000000000000000000000000000000000000000064bc539f00000000000000000000000000000000000000000000000000000000000000020000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34fc0",
+ "0x02f9013082e7080e846548b1a4846548b1ae8302cdc594c66149996d0263c0b42d3bc05e50db88658106ce80b9010418cbafe5000000000000000000000000000000000000000000000000a688906bd8b000000000000000000000000000000000000000000000000000000015f4b85f94d69f00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000005029056a3581629953feba7cba2446b6057295160000000000000000000000000000000000000000000000000000000064bc53a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34fc0",
+ "0x02f9013082e7080e846548b1a4846548b1ae8302cdc594c66149996d0263c0b42d3bc05e50db88658106ce80b9010418cbafe50000000000000000000000000000000000000000000000009cd1aa2149ea00000000000000000000000000000000000000000000000000000014acf9cb1dc31100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000326b931b50a190704341c4f832ebd7a75f4e86290000000000000000000000000000000000000000000000000000000064bc539f00000000000000000000000000000000000000000000000000000000000000020000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34fc0",
+ "0x02f9013082e7080e846548b1a4846548b1ae8302cdc594c66149996d0263c0b42d3bc05e50db88658106ce80b9010418cbafe50000000000000000000000000000000000000000000000009f98351204fe000000000000000000000000000000000000000000000000000000150a9e61f0246600000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000d7c0fb08b29ea8bbca31b51d6d7bebadf288e2af0000000000000000000000000000000000000000000000000000000064bc539f00000000000000000000000000000000000000000000000000000000000000020000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34fc0",
+ "0x02f9023782e70816846548b1a4846548b1ae83054dfe94438670d41d5118003b2f42cc0466fbadd760dbf4871d2b46f19c59e6b90204ac9650d800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000104c238a3a30000000000000000000000000000000000000000000000000000000000000fcbfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeccf8fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed0e0000000000000000000000000000000000000000000000001f9129260ed919f98000000000000000000000000000000000000000000000000001d2b46f19c59e6000000000000000000000000000000000000000000000001d1781783df86e0cf000000000000000000000000000000000000000000000000001902f4b0f0b9cf0000000000000000000000000000000000000000000000000000000064bc53930000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041faa413300000000000000000000000000000000000000000000000000000000c0",
+ "0x02ed82e70880846548b1a4846548b1ae82b22494cce9d3f392c135dc038b147ca73ec496f7f89d938084183ff085c0",
+ "0x02f86e82e70803846548b1a4846548b1ae82fb2a9466627f389ae46d881773b7131139b2411980e09e80b844095ea7b3000000000000000000000000438670d41d5118003b2f42cc0466fbadd760dbf4000000000000000000000000000000000000000000000000000000000046b7f0c0",
+ "0x02f9013082e7080f846548b1a4846548b1ae8302cdc594c66149996d0263c0b42d3bc05e50db88658106ce80b9010418cbafe50000000000000000000000000000000000000000000000009f98351204fe000000000000000000000000000000000000000000000000000000150a9e61f0246600000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000cc6fc47b6a3a03b41f43fd33c699a1ac32e3b6910000000000000000000000000000000000000000000000000000000064bc53a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34fc0",
+ "0x02f9013082e7080d846548b1a4846548b1ae8302cdc594c66149996d0263c0b42d3bc05e50db88658106ce80b9010418cbafe5000000000000000000000000000000000000000000000000a25ec002c012000000000000000000000000000000000000000000000000000000156842a209508f00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000002183ba8662f9f0e1bebffab11e31ba8cae53bcec0000000000000000000000000000000000000000000000000000000064bc539f00000000000000000000000000000000000000000000000000000000000000020000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34fc0",
+ "0x02ed82e70880846548b1a4846548b1ae82b22494cce9d3f392c135dc038b147ca73ec496f7f89d938084183ff085c0",
+ "0x02f86e82e7080b846548b1a4846548b1ae82b7679466627f389ae46d881773b7131139b2411980e09e80b844095ea7b3000000000000000000000000272e156df8da513c69cb41cc7a99185d53f926bb000000000000000000000000000000000000000000000000000000001ee98853c0",
+ "0x02f86f82e7080b846548b1a4846548b1ae8301042f947d43aabc515c356145049227cee54b608342c0ad80b844095ea7b3000000000000000000000000438670d41d5118003b2f42cc0466fbadd760dbf4ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0",
+ "0x02f082e7080f84623ed54584623ed54f8252089445a318273749d6eb00f5f6ca3bc7cd3de26d642a87b8bdb97852000280c0"
+ ],
+ "l2ToL1MsgHashes": [],
+ "timestamp": 1690062599,
+ "rootHash": "0x290c378f1d39464c72c71773d84030cbf3d354bca03f92caae4139a06876790f",
+ "fromAddresses": "0xc1c6b09d1eb6fca0ff3ca11027e5bc4aedb47f6780c67432656d59144ceff962e8faf8926599bcf86810dba12317406828d64b07d34ad22338644bb3c4f53c1aeb4e8f12b2c427e7a4505cd189be3392126824925a67f98dca1eb9d92d78d51c99f3a427a8a71d5fb2d5fbc8f31799c5cdd8ae67722885a552e085691b4ae78da51187a5e9d8381f08fe2bd392b1a1c7c7542e23a65e528d2d58a891c9aa475978ea6f35af55843f9d1d654f4a076ef435fa34695029056a3581629953feba7cba2446b605729516326b931b50a190704341c4f832ebd7a75f4e8629d7c0fb08b29ea8bbca31b51d6d7bebadf288e2af4c9728684fd37254a82956ad9a8a87bbd938364a7a145a31686980565976eae84024d4ca9c06bda82d584a6d566ea2cee64ea18dddbcddacbb7f681fcc6fc47b6a3a03b41f43fd33c699a1ac32e3b6912183ba8662f9f0e1bebffab11e31ba8cae53bcec359084e2583e835807164c0f864ecff655d1849f4de89c2ae04a31cc2dba3e2da11a38ddc43682b90ff2e6c1d14ce8a94e74d0eb9d6f2536363aeb409232224978f9b83b580ba028bdab92d7976ab41f",
+ "batchReceptionIndices": []
+ },
+ {
+ "rlpEncodedTransactions": [
+ "0x02f9013282e70882b240846d0182b0846d0182be8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000090ae52f244688d687b693d99769e455a38de9b2b00000000000000000000000090ae52f244688d687b693d99769e455a38de9b2b00000000000000000000000000000000000000000000000000013811a83d9bc0000000000000000000000000000000000000000000000000002aa1efb94e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd090000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b241846d0182b0846d0182be8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000052df1b28c2afc2cbb0b320fd787a7844c6e580cd00000000000000000000000052df1b28c2afc2cbb0b320fd787a7844c6e580cd00000000000000000000000000000000000000000000000000013811a83d9bc0000000000000000000000000000000000000000000000000002aa1efb94e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd0a0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b242846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000000bda323c78a069cee9d2265ced3bcfa1332ba7070000000000000000000000000bda323c78a069cee9d2265ced3bcfa1332ba7070000000000000000000000000000000000000000000000000002e50893ba5a59000000000000000000000000000000000000000000000000001b0d7ea876e025000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd0b0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b243846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000b9652a464ee262d5aedc9189fcb59ea04441b21a000000000000000000000000b9652a464ee262d5aedc9189fcb59ea04441b21a00000000000000000000000000000000000000000000000000031b636ca3611e000000000000000000000000000000000000000000000000001a66ab71d3c9ad000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd0c0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b244846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000d1a6dc9b8762fe8c93c5ac103a1e365cc9fb5a06000000000000000000000000d1a6dc9b8762fe8c93c5ac103a1e365cc9fb5a060000000000000000000000000000000000000000000000000003391f90d6498f000000000000000000000000000000000000000000000000001ab250d3446d10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd0e0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b245846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000a9b13af6181ff98c9eeaf9ca46095ee2f916f832000000000000000000000000a9b13af6181ff98c9eeaf9ca46095ee2f916f83200000000000000000000000000000000000000000000000000028c843d42655a000000000000000000000000000000000000000000000000001a62ddacb258dd000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd0d0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b246846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000004acc7828d76abf4f84105f7c826a0d52b00ba38a0000000000000000000000004acc7828d76abf4f84105f7c826a0d52b00ba38a0000000000000000000000000000000000000000000000000002827a4d2c36e8000000000000000000000000000000000000000000000000001b5434999b29e6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd100000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b247846d0182b0846d0182be8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000027bc8621471d8d84475a211fb7556c158958d0d200000000000000000000000027bc8621471d8d84475a211fb7556c158958d0d20000000000000000000000000000000000000000000000000001429fc0610640000000000000000000000000000000000000000000000000001c110215b9c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd110000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b248846d0182b0846d0182be8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000007e4e0c3be87aef0e846162c40d96c7ffbcf1ca130000000000000000000000007e4e0c3be87aef0e846162c40d96c7ffbcf1ca1300000000000000000000000000000000000000000000000000013811a83d9bc000000000000000000000000000000000000000000000000000f8b0a10e470000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd130000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b249846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000ded87809191d1ce529b875d81478f08c4e7e1028000000000000000000000000ded87809191d1ce529b875d81478f08c4e7e10280000000000000000000000000000000000000000000000000001967d1439851800000000000000000000000000000000000000000000000000523f1929090ebc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd120000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b24a846d0182b0846d0182be8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000004f579b878eebf064b1d1f1d8b8032f869ec650190000000000000000000000004f579b878eebf064b1d1f1d8b8032f869ec6501900000000000000000000000000000000000000000000000000013e2428d4b4e00000000000000000000000000000000000000000000000000017cd9d4ffec000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd140000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b24b846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000e512e419213e04728cf87c784620cd1bb0e91fdc000000000000000000000000e512e419213e04728cf87c784620cd1bb0e91fdc00000000000000000000000000000000000000000000000000025ab9cef6d3b6000000000000000000000000000000000000000000000000001c3ef9184c62bb000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd190000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b24c846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000004667317607f05721a60b69e2da3254b58b4fa2150000000000000000000000004667317607f05721a60b69e2da3254b58b4fa21500000000000000000000000000000000000000000000000000027612ae8381aa000000000000000000000000000000000000000000000000001b3ff4d6abb3dc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd170000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b24d846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000a91a200fd2959c0a3bb530eb6e14c5376fadcce3000000000000000000000000a91a200fd2959c0a3bb530eb6e14c5376fadcce300000000000000000000000000000000000000000000000000024479a76f5533000000000000000000000000000000000000000000000000001aedb721196262000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd180000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b24e846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000a9b49485282fe1d16a1e7dffab0378fd2922e27f000000000000000000000000a9b49485282fe1d16a1e7dffab0378fd2922e27f000000000000000000000000000000000000000000000000000233e4626489ee000000000000000000000000000000000000000000000000001b36c2a7ac1d62000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd150000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b24f846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000059ad5ae25a4c2e40ef2fb5df27db491af72d6d4200000000000000000000000059ad5ae25a4c2e40ef2fb5df27db491af72d6d420000000000000000000000000000000000000000000000000002b7ebd3b6c5f3000000000000000000000000000000000000000000000000001c6d36103d6026000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd160000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b250846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000c754a76208d323fabfb5e60b5cd6602673236bb3000000000000000000000000c754a76208d323fabfb5e60b5cd6602673236bb300000000000000000000000000000000000000000000000000022fbb5512f782000000000000000000000000000000000000000000000000001b7679a96d2844000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd1a0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b251846d0182b0846d0182be8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000001ed2d8eebcd1d0319a4e5374bdbed288589f00a10000000000000000000000001ed2d8eebcd1d0319a4e5374bdbed288589f00a10000000000000000000000000000000000000000000000000002691003e5e8d6000000000000000000000000000000000000000000000000001b10359ee7ad8f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd1e0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b252846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000dfa435f8df3036bab2e1ffe4923840178ea54c3e000000000000000000000000dfa435f8df3036bab2e1ffe4923840178ea54c3e0000000000000000000000000000000000000000000000000002a991e6a46a9a000000000000000000000000000000000000000000000000001bff45bf1da75e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd1d0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b253846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000feffbb43619cad27dc96a60618495be1a8882f5b000000000000000000000000feffbb43619cad27dc96a60618495be1a8882f5b00000000000000000000000000000000000000000000000000026a801a888074000000000000000000000000000000000000000000000000001b103c21e6c968000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd1b0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b254846d0182b0846d0182be8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000e6f9e9783211e6d22108bf30d448e27694479d38000000000000000000000000e6f9e9783211e6d22108bf30d448e27694479d3800000000000000000000000000000000000000000000000000016b3f8f9ae760000000000000000000000000000000000000000000000000001cc6e836ae4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd1c0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b255846d0182b0846d0182be8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000dae2f9005c7c2bb2a29bf4eee6ee8c29cc3af7ff000000000000000000000000dae2f9005c7c2bb2a29bf4eee6ee8c29cc3af7ff000000000000000000000000000000000000000000000000000237fe04de55fc000000000000000000000000000000000000000000000000001bf2081e5ca582000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd1f0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b256846d0182b0846d0182be8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000b25cb5c1c5ac5b59971e907ed97e90364b760e30000000000000000000000000b25cb5c1c5ac5b59971e907ed97e90364b760e3000000000000000000000000000000000000000000000000000016b3f8f9ae760000000000000000000000000000000000000000000000000001c3e7b9df6e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd200000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b257846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000004d58159ed0e1fa7992970875e7860bb9755d4e040000000000000000000000004d58159ed0e1fa7992970875e7860bb9755d4e04000000000000000000000000000000000000000000000000000228d889a3d34a000000000000000000000000000000000000000000000000001b3c16d950ce97000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd210000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b258846d0182b0846d0182be8301361d94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000ee99f3fb8744dd05cbbd87104b7f088d194992ae000000000000000000000000ee99f3fb8744dd05cbbd87104b7f088d194992ae00000000000000000000000000000000000000000000000000016b3f8f9ae760000000000000000000000000000000000000000000000000016345785d8a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd230000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b259846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000076d696f8f3f5904aecf0f8982b82f88d80228f6700000000000000000000000076d696f8f3f5904aecf0f8982b82f88d80228f6700000000000000000000000000000000000000000000000000029a2f0df23dd9000000000000000000000000000000000000000000000000001c63e6326e3509000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd220000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b25a846d0182b0846d0182be8301361194508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000f2ed2042f3bf1c9e9ec3de5a79d6b8334e498d20000000000000000000000000f2ed2042f3bf1c9e9ec3de5a79d6b8334e498d2000000000000000000000000000000000000000000000000000016b3f8f9ae76000000000000000000000000000000000000000000000000000470de4df820000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd250000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b25b846d0182b0846d0182be8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000006bd99000b5874c02e666ced80905f038024296900000000000000000000000006bd99000b5874c02e666ced80905f03802429690000000000000000000000000000000000000000000000000000299cc8b064176000000000000000000000000000000000000000000000000001a7abad9e76168000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd240000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b25c846d0182b0846d0182be8301993a94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000008a8625919bfa16c58f5c3aa900c616be91955c520000000000000000000000008a8625919bfa16c58f5c3aa900c616be91955c5200000000000000000000000000000000000000000000000000016b3f8f9ae760000000000000000000000000000000000000000000000000001ff973cafa8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd270000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b25d846d0182b0846d0182be8301361194508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000000a6f2f5ad8954edbadfdb508e599cd26ab668b970000000000000000000000000a6f2f5ad8954edbadfdb508e599cd26ab668b97000000000000000000000000000000000000000000000000000155aa30e6ad80000000000000000000000000000000000000000000000000002386f26fc10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd290000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b25e846d0182b0846d0182be8301361d94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000c1ea3db638495d90bb778863ff5c2aaccfbbf421000000000000000000000000c1ea3db638495d90bb778863ff5c2aaccfbbf42100000000000000000000000000000000000000000000000000016b3f8f9ae76000000000000000000000000000000000000000000000000000c3663566a58000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd2a0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f482e708823ed285012a05f200852e90edd000825208949d0e0c96288a3ebdda1bd73523aeb8e262c6e679872714711487800580c0",
+ "0x02f482e70882402185012a05f200852e90edd0008252089468ad1fa00cb9d499b73e85c6449766374463b6b2872386f26fc1080980c0",
+ "0x02f482e708823ed385012a05f200852e90edd00082520894fa8d660cfa214e3071b915d5bb88c75834e9a15587470de4df82005080c0",
+ "0x02f84d82e70808846ba55515846ba55515828cbb94e5d7c2a44ffddf6b295a15c148167daaaf5cf34f80a42e1a7d4d000000000000000000000000000000000000000000000000000d51e75245751bc0",
+ "0x02f86e82e7080b846ba55515846ba5551582b4fb947d43aabc515c356145049227cee54b608342c0ad80b844095ea7b300000000000000000000000080e38291e06339d10aab483c65695d004dbd5c690000000000000000000000000000000000000000000000014546d1a4322aca3ac0",
+ "0x02f9029782e70801846ba55515846ba5551583023e759480e38291e06339d10aab483c65695d004dbd5c69874c1522a7a7ae9cb902642cc4081e000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000224ec2fcd3bcafda90000000000000000000000000000000000000000000000000000000064bc7ded0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004c1522a7a7ae9c000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000007f72e0d8e9abf9133a92322b8b50bd8e0f9dcfcb0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f000000000000000000000000de705471bb4414bfdabe09f52665914ed0e5725400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f86e82e7080e846548b1a4846548b1ae82b3e494e5d7c2a44ffddf6b295a15c148167daaaf5cf34f80b844095ea7b3000000000000000000000000272e156df8da513c69cb41cc7a99185d53f926bb000000000000000000000000000000000000000000000000001a711930b89000c0",
+ "0x02f86e82e70838846548b1a4846548b1ae82b4fb947d43aabc515c356145049227cee54b608342c0ad80b844095ea7b300000000000000000000000080e38291e06339d10aab483c65695d004dbd5c6900000000000000000000000000000000000000000000000c31f1cf83c3d8fb3ec0",
+ "0x02f482e7080a84623ed54584623ed54f83014cab94a02573c4ad15c16b48f10842aac9c9ea405b65a386d12f0c4c6000841249c58bc0",
+ "0x02f582e7080184623ed54584623ed54f83012779943c5b31b158dcaba76df46bd853c8af3ccf0f002287016bcc41e90000841249c58bc0",
+ "0x02f9027082e7080784623ed54584623ed54f8302d78c94272e156df8da513c69cb41cc7a99185d53f926bb80b90244ac9650d800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000104a8c9ed670000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f000000000000000000000000000000000000000000000000000000000000012c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064bc53ab0000000000000000000000000000000000000000000000001f9a0ef077f3ff15000000000000000000000000000000000000000000000000000444a1b61145790000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044bac37ef7000000000000000000000000000000000000000000000000000444a1b611457900000000000000000000000017cec49895644f84c50809d874e22feb923553d800000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9023782e7084284623ed54584623ed54f83035d6494272e156df8da513c69cb41cc7a99185d53f926bb8701e794b458ec96b90204ac9650d800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000104bfba6b22000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f0000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000000000000000000000000000000000000000012c000000000000000000000000a6dff484a46f90e5d03ff91e766ac70cf1e0eecb0000000000000000000000000000000000000000000000000000000064bc53870000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000001e794b458ec9600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041faa413300000000000000000000000000000000000000000000000000000000c0",
+ "0x02f901d082e7080484623ed54584623ed54f8307359694438670d41d5118003b2f42cc0466fbadd760dbf480b901a4ea5406320000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f000000000000000000000000000000000000000000000000000000000000012cfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed928fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed9dcfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed8ecfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed9a000000000000000000000000000000000000000000000000032c969648a3f809200000000000000000000000000000000000000000000000000060a24181e40000000000000000000000000000000000000000000000000001864ecab95235e0600000000000000000000000000000000000000000000000000026db13ab8909c00000000000000000000000038022d79892c57761a67ab251c7d70a55dbc87120000000000000000000000000000000000000000000000000000000064bc53abc0",
+ "0x02f8f782e7081084623ed54584623ed54f8302cbbc94c66149996d0263c0b42d3bc05e50db88658106ce8803a2956f887d21acb8c4f305d7190000000000000000000000009201f3b9dfab7c13cd659ac5695d12d605b5f1e600000000000000000000000000000000000000000000004dea22ce324f339a3a00000000000000000000000000000000000000000000004d8667c05d93ecf1c6000000000000000000000000000000000000000000000000039dee49db01a039000000000000000000000000633eb62c0a2a7c6ff9d03f801b1adfa5f02cc55b0000000000000000000000000000000000000000000000000000000064bc538cc0",
+ "0x02f86e82e7081384623ed54584623ed54f82b4fb945471ea8f739dd37e9b81be9c5c77754d8aa953e480b844095ea7b3000000000000000000000000272e156df8da513c69cb41cc7a99185d53f926bb0000000000000000000000000000000000000000000000000a1a9ce0f703af0ac0"
+ ],
+ "l2ToL1MsgHashes": [],
+ "timestamp": 1690062611,
+ "rootHash": "0x1f00ce5b9e15ff5f9cbcba3ab213ca2c22dabecbe5f8a902553aedef133152b3",
+ "fromAddresses": "0x46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b80c67432656d59144ceff962e8faf8926599bcf8e4edb277e41dc89ab076a1f049f4a3efa700bce880c67432656d59144ceff962e8faf8926599bcf827d967c1a5636fcea985f696525bb4f911dd5c59793560c4e66c450545bc54271f84b17d989c5ecbde705471bb4414bfdabe09f52665914ed0e57254d59c3b026e9abef3f744491462d369e4c27040b312e7301eef23a02c5162dce59c0b8ba264dc92e46abc316192b43dc64c79ec841ac9546f793022c420bae152846374d7572c1fcfe182e6bb5cfcb13117cec49895644f84c50809d874e22feb923553d8a6dff484a46f90e5d03ff91e766ac70cf1e0eecb38022d79892c57761a67ab251c7d70a55dbc8712633eb62c0a2a7c6ff9d03f801b1adfa5f02cc55b5636d6f8810951d686a22da016654f22efdda85e",
+ "batchReceptionIndices": []
+ },
+ {
+ "rlpEncodedTransactions": [
+ "0x02f9013282e70882b25f846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000007fbebf89ec31747099485233aacef5e3f39fd5950000000000000000000000007fbebf89ec31747099485233aacef5e3f39fd59500000000000000000000000000000000000000000000000000024523c543d459000000000000000000000000000000000000000000000000001b60f3f6387fe2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd260000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b260846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000a885747c9baade27c1a57d5747ad128e3d069bb5000000000000000000000000a885747c9baade27c1a57d5747ad128e3d069bb50000000000000000000000000000000000000000000000000002858ff455d411000000000000000000000000000000000000000000000000001b7919966d30ba000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd2b0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b261846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000e6dbe9a315aa775a52a749923accbc752665a2db000000000000000000000000e6dbe9a315aa775a52a749923accbc752665a2db0000000000000000000000000000000000000000000000000002a19565cc15f9000000000000000000000000000000000000000000000000001d2b3e94606485000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd2c0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b262846d0182b0846d0182be8301993a94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000001f657297fbe86fb4bf3ebe00d47a7f047cf5e8f40000000000000000000000001f657297fbe86fb4bf3ebe00d47a7f047cf5e8f400000000000000000000000000000000000000000000000000014d8f7e915b2000000000000000000000000000000000000000000000000000205466db74c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd2d0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b263846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000079e8da554811076d8df8975c2175c696c438592b00000000000000000000000079e8da554811076d8df8975c2175c696c438592b00000000000000000000000000000000000000000000000000032e61fb77d53b000000000000000000000000000000000000000000000000001bf05f22026dd8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd2f0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b264846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000084802571e30720a5dde1af48b84915d0a7d9d2a900000000000000000000000084802571e30720a5dde1af48b84915d0a7d9d2a90000000000000000000000000000000000000000000000000002c4c9673ddda2000000000000000000000000000000000000000000000000001b39ac02dbb9c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd2e0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b265846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000007fa3116b528f8941e6e8530e4a36d0a9d80574db0000000000000000000000007fa3116b528f8941e6e8530e4a36d0a9d80574db0000000000000000000000000000000000000000000000000003118f508e1ad3000000000000000000000000000000000000000000000000001ba80bb438c2f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd300000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b266846d0182b0846d0182be8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000a5d3299a38b5d0020a1690f11f8450c9e197d654000000000000000000000000a5d3299a38b5d0020a1690f11f8450c9e197d65400000000000000000000000000000000000000000000000000016b3f8f9ae760000000000000000000000000000000000000000000000000001cc6e836ae4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd330000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b267846d0182b0846d0182be8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000078874c29e4a3de39618e9bc39a239bd008c6472e00000000000000000000000078874c29e4a3de39618e9bc39a239bd008c6472e00000000000000000000000000000000000000000000000000014d8f7e915b20000000000000000000000000000000000000000000000000001d7cce57a2c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd320000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b268846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000570e9127026d8764bf8402fe10bfd7dcbb665ed1000000000000000000000000570e9127026d8764bf8402fe10bfd7dcbb665ed100000000000000000000000000000000000000000000000000028aaaa249f44e000000000000000000000000000000000000000000000000001c0b062dd167a6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd310000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b269846d0182b0846d0182be8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000d33e335e680198a0c141b8ae5769498a000fc998000000000000000000000000d33e335e680198a0c141b8ae5769498a000fc9980000000000000000000000000000000000000000000000000002f55c5d4b2dc2000000000000000000000000000000000000000000000000001be650d1ea042c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd340000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b26a846d0182b0846d0182be8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000004f6b45db2da225dc9dfa29eb258d3d63018c72060000000000000000000000004f6b45db2da225dc9dfa29eb258d3d63018c72060000000000000000000000000000000000000000000000000001429fc0610640000000000000000000000000000000000000000000000000002081e063b1e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd360000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b26b846d0182b0846d0182be8301993a94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000072d0a5f5f802ca3950d9ff8abf467e6b1465d43900000000000000000000000072d0a5f5f802ca3950d9ff8abf467e6b1465d4390000000000000000000000000000000000000000000000000001438dbfe44300000000000000000000000000000000000000000000000000001c6bf526340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd350000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b26c846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000051870c7d99f761b9297433be7256ea24290a0bbd00000000000000000000000051870c7d99f761b9297433be7256ea24290a0bbd00000000000000000000000000000000000000000000000000024e14ee9dad98000000000000000000000000000000000000000000000000001c058614b05ed4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd370000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b26d846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000cc790b22ef435b76da0bb8d7c239fbf919655761000000000000000000000000cc790b22ef435b76da0bb8d7c239fbf919655761000000000000000000000000000000000000000000000000000298197090db88000000000000000000000000000000000000000000000000001cb13f9084f640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd380000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b26e846d1e961f846d1e962d8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000001e9a07ffcc98a932aa57fa57407833ec0adc6bb00000000000000000000000001e9a07ffcc98a932aa57fa57407833ec0adc6bb0000000000000000000000000000000000000000000000000002f0bfa7b63dae000000000000000000000000000000000000000000000000001c9da97501e879000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd390000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b26f846d1e961f846d1e962d8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000876199f2d7a3463179dc26c770fccb378a1dad22000000000000000000000000876199f2d7a3463179dc26c770fccb378a1dad220000000000000000000000000000000000000000000000000002f2078c9f9f67000000000000000000000000000000000000000000000000001c16b42e7835d2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd3b0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b270846d1e961f846d1e962d8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000a4734b099118455730d1a845dbdfd0725dc6a61a000000000000000000000000a4734b099118455730d1a845dbdfd0725dc6a61a0000000000000000000000000000000000000000000000000002fef25c863b85000000000000000000000000000000000000000000000000001c5f4e94f95862000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd3c0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b271846d1e961f846d1e962d8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000a211bd69c8a1400cb429a1ff92ad292489dc7f62000000000000000000000000a211bd69c8a1400cb429a1ff92ad292489dc7f620000000000000000000000000000000000000000000000000001429fc0610640000000000000000000000000000000000000000000000000000e35fa931a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd3d0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b272846d1e961f846d1e962d8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000c2f1d21cd496bea3a8361daaee75101df4d27ea7000000000000000000000000c2f1d21cd496bea3a8361daaee75101df4d27ea70000000000000000000000000000000000000000000000000001f0b2c94f77c0000000000000000000000000000000000000000000000000002e2f6e5e148000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd3e0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b273846d1e961f846d1e962d8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000b466fd02e3e151bcbce9c97fb0a9395889114df6000000000000000000000000b466fd02e3e151bcbce9c97fb0a9395889114df60000000000000000000000000000000000000000000000000002ddeda9078707000000000000000000000000000000000000000000000000001d11453af6ce96000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd3a0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b274846d1e961f846d1e962d8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000ac2314f552fe74262d6238bd4a6af9b910bd92b7000000000000000000000000ac2314f552fe74262d6238bd4a6af9b910bd92b70000000000000000000000000000000000000000000000000002c8d66ed8a114000000000000000000000000000000000000000000000000001ae9d2ef83c6e8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd3f0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b275846d1e961f846d1e962d8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000a4fe2ec308b39ded3fa9528efb798cd13a4f267e000000000000000000000000a4fe2ec308b39ded3fa9528efb798cd13a4f267e000000000000000000000000000000000000000000000000000300330dd90090000000000000000000000000000000000000000000000000001aa0fa68dac85c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd400000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b276846d1e961f846d1e962d8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000064531e1da97c2af6108a03d9c49a2e47b5bb71f100000000000000000000000064531e1da97c2af6108a03d9c49a2e47b5bb71f1000000000000000000000000000000000000000000000000000274797fa0c06d000000000000000000000000000000000000000000000000001c4a56c7f6b6d4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd410000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b277846d1e961f846d1e962d8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000059780ded40a5c540a536644dae651c54504dcb4400000000000000000000000059780ded40a5c540a536644dae651c54504dcb4400000000000000000000000000000000000000000000000000028b423ed3116f000000000000000000000000000000000000000000000000001b943596512384000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd440000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b278846d1e961f846d1e962d8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000524a06bada4d98aa7c96275b7d7130d3180b0ff8000000000000000000000000524a06bada4d98aa7c96275b7d7130d3180b0ff80000000000000000000000000000000000000000000000000002b55a3ca2b094000000000000000000000000000000000000000000000000001ab7f60b5414c4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd430000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b279846d1e961f846d1e962d8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000a3fa534de502a31714353d91f26e1e77f6084323000000000000000000000000a3fa534de502a31714353d91f26e1e77f6084323000000000000000000000000000000000000000000000000000139d8db69f480000000000000000000000000000000000000000000000000001f438daa060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd450000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b27a846d1e961f846d1e962d8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000001d35a60b0b6815cac022f0ff5bffe4371a23ec460000000000000000000000001d35a60b0b6815cac022f0ff5bffe4371a23ec46000000000000000000000000000000000000000000000000000139d8db69f48000000000000000000000000000000000000000000000000000354a6ba7a18000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd460000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b27b846d1e961f846d1e962d8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000093f09eb91afcd7d4de05dbdfadde30d53d20580200000000000000000000000093f09eb91afcd7d4de05dbdfadde30d53d205802000000000000000000000000000000000000000000000000000241b57c404012000000000000000000000000000000000000000000000000001c4b5e4c48b40a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd470000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b27c846d1e961f846d1e962d8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000dd47d3db30809a107c1dd59417a61230458d278e000000000000000000000000dd47d3db30809a107c1dd59417a61230458d278e000000000000000000000000000000000000000000000000000139d8db69f480000000000000000000000000000000000000000000000000006a94d74f430000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd490000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b27d846d1e961f846d1e962d8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000863ca2c8116aaa8d8674a0b3491fb9fbb99ee706000000000000000000000000863ca2c8116aaa8d8674a0b3491fb9fbb99ee706000000000000000000000000000000000000000000000000000139d8db69f480000000000000000000000000000000000000000000000000000aa87bee538000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd4b0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b27e846d1e961f846d1e962d8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000012cb6f5eb0d90f8978deb060c46539e8b872550900000000000000000000000012cb6f5eb0d90f8978deb060c46539e8b87255090000000000000000000000000000000000000000000000000001398c01372ae0000000000000000000000000000000000000000000000000002aa1efb94e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd4c0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b27f846d1e961f846d1e962d8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000245722555190dc99ee569cdd1c5a67e05ce62dfa000000000000000000000000245722555190dc99ee569cdd1c5a67e05ce62dfa0000000000000000000000000000000000000000000000000002c38faa4ee925000000000000000000000000000000000000000000000000001c0e839c49bb12000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd480000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b280846d1e961f846d1e962d8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000da9a19e7831402175051adc494f67146e5827e43000000000000000000000000da9a19e7831402175051adc494f67146e5827e4300000000000000000000000000000000000000000000000000013498d5b048e0000000000000000000000000000000000000000000000000001ff973cafa8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd4a0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b281846d1e961f846d1e962d8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000099877f4429978df7552fde876e0eec345497444600000000000000000000000099877f4429978df7552fde876e0eec345497444600000000000000000000000000000000000000000000000000013498d5b048e0000000000000000000000000000000000000000000000000000aa87bee538000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd4d0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f482e70882402285012a05f200852e90edd000825208942aafe94148739a6e868e32cf8b1ca9fbc81e2d0987121e6c485ac03780c0",
+ "0x02f482e708823ed485012a05f200852e90edd000825208941981f7efb48591ee64bdaf0d61fe042e39fc12e3872386f26fc1001c80c0",
+ "0x02f482e70882402385012a05f200852e90edd000825208940d8c44854055f4becdfee63d2f6286548ace0c34871550f7dca7028780c0",
+ "0x02f9029782e70880846ba55515846ba55515830282449480e38291e06339d10aab483c65695d004dbd5c69871d21db47288000b902642cc4081e00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000010d92ea475e7ce7010000000000000000000000000000000000000000000000000000000064bc7def0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001d21db472880000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000046ec4bb184528c3aee6f1419e11b28a97f33d4830000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f000000000000000000000000a550c99253c5ee0136ac37eb610a28dfe2cc093a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9019782e7080a84623ed54584623ed54f8307a12094da4c3eb39707ad82ea7a31afd42bdf850fed8f418709a12f75a9e800b901649caf2b9700000000000000000000000000000000000000000000000000000000000000e00000000000000000000000009dd711b0cb4430f429231e5cb9940dbd1952a36f0000000000000000000000000000000000000000000000000000000001e13380000000000000000000000000000000000000000000000000000000000000012000000000000000000000000007039fb83798d979bc4697ab4e3e1bb715bf95700000000000000000000000009dd711b0cb4430f429231e5cb9940dbd1952a36f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005736869667400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000056c696e6561000000000000000000000000000000000000000000000000000000c0",
+ "0x02f901b782e7081184623ed54584623ed54f8321710694a02573c4ad15c16b48f10842aac9c9ea405b65a38701d704a97b9400b901845190563600000000000000000000000069421eeb27ca0941bf46e071098172a56b35df97000000000000000000000000000000000000000000000000000000000000006600000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000003d2a1800000000000000000000000069421eeb27ca0941bf46e071098172a56b35df9700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000001469421eeb27ca0941bf46e071098172a56b35df97000000000000000000000000000000000000000000000000000000000000000000000000000000000000002200010000000000000000000000000000000000000000000000000000000000030d40000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f86e82e7080484623ed54584623ed54f82b3cc94e5d7c2a44ffddf6b295a15c148167daaaf5cf34f80b844095ea7b3000000000000000000000000438670d41d5118003b2f42cc0466fbadd760dbf4000000000000000000000000000000000000000000000000008e1bc9bf040000c0",
+ "0x02f86e82e7083184623ed54584623ed54f82721794265b25e22bcd7f10a5bd6e6410f10537cc7567e880b844095ea7b3000000000000000000000000438670d41d5118003b2f42cc0466fbadd760dbf400000000000000000000000000000000000000000000006c6b935b8bbd400000c0",
+ "0x02f901b682e7080584623ed54584623ed54f8303b8da949e66eba102b77fc75cd87b5e60141b85573bc8e88695a0387c24e7b9018451905636000000000000000000000000f4c21df1cc8dfca3b064e9b3c9a0f434c3b9039f00000000000000000000000000000000000000000000000000000000000000b800000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000017d81d6000000000000000000000000f4c21df1cc8dfca3b064e9b3c9a0f434c3b9039f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000014f4c21df1cc8dfca3b064e9b3c9a0f434c3b9039f000000000000000000000000000000000000000000000000000000000000000000000000000000000000002200010000000000000000000000000000000000000000000000000000000000055730000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013082e7080b84623ed54584623ed54f83019a6894508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000ee99f3fb8744dd05cbbd87104b7f088d194992ae000000000000000000000000ee99f3fb8744dd05cbbd87104b7f088d194992ae00000000000000000000000000000000000000000000000000016b3f8f9ae760000000000000000000000000000000000000000000000000016345785d8a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd230000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f86e82e7084484623ed54584623ed54f82b507947d43aabc515c356145049227cee54b608342c0ad80b844095ea7b3000000000000000000000000c66149996d0263c0b42d3bc05e50db88658106ce0000000000000000000000000000000000000000000000020ddba8ea3d826848c0",
+ "0x02f8b682e7081184623ed54584623ed54f829a3e94d9d74a29307cc6fc8bf424ee4217f1a587fbc8dc881af0e2108677632cb88429723511000000000000000000000000e4edb277e41dc89ab076a1f049f4a3efa700bce8000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000200325a9ead0f563f2ccf8c7afcf1f277a42d7bd16c676881b19cc96d467244f7ac0",
+ "0x02f9027082e7080a84623ec1c084623ec1c08302d77e94272e156df8da513c69cb41cc7a99185d53f926bb80b90244ac9650d800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000104a8c9ed670000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f000000000000000000000000000000000000000000000000000000000000012c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064bc539f000000000000000000000000000000000000000000000000821ab0d4414980000000000000000000000000000000000000000000000000000011925a4182e4820000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044bac37ef70000000000000000000000000000000000000000000000000011925a4182e482000000000000000000000000be8c5b0bdfb7b19c08fbad3c085b05da15cb721c00000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013782e70880845f34f8e7845f34f8ee830390f994272e156df8da513c69cb41cc7a99185d53f926bb878e1bc9bf040000b90104a8c9ed67000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f00000000000000000000000066627f389ae46d881773b7131139b2411980e09e000000000000000000000000000000000000000000000000000000000000012c0000000000000000000000001e7bb53c1eefa4513b69fcbfa900b6bce051a77b0000000000000000000000000000000000000000000000000000000064bc539f000000000000000000000000000000000000000000000000008e1bc9bf040000000000000000000000000000000000000000000000000000000000000464229e0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9047782e70820845d5a946d845d5a947783032773941a7b46c660603ebb5fbe3ae51e80ad21df00bdd1870d801472258000b90444a71c9b7f00000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000001b5722833b955e27a35a12eeab4886b650ad810b39e0de5a25fc177912a8bf476e2a99c9092df5e2f398069970b339375fd1aba8d53eb271e4f1fc1f749d7e5eb70000000000000000000000000000000000000000000000000000000064bc4de3000000000000000000000000b1ebe120c810ab46afcbcbdad7102096fb12634300000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b1ebe120c810ab46afcbcbdad7102096fb126343000000000000000000000000000000000000000000000000000bd011e3e0d0000000000000000000000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064c57e07000000000000000000000000e49cf1bbb229562aea4045133dfd244676b3acb8000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000b62c414abf83c0107db84f8de1c88631c05a8d7b0000000000000000000000000000000000000000000000000000000000000f1000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000955af4de9ca03f84c9462457d075acabf1a8afc80000000000000000000000000000000000000000000000000001599ba503c0000000000000000000000000000000000000000000000000000000000000000041dfb16404843d231528aaba3eaa2193cfc1ce0b72893909d63a87e98524f3b08c4b61dce555815a6366ef6e5c18f6c9832f9d13cc49d81e180429ac8babb9009d1c00000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f84d82e7080b845d4cdd5e845d4cdd5e828caf94e5d7c2a44ffddf6b295a15c148167daaaf5cf34f80a42e1a7d4d0000000000000000000000000000000000000000000000000006b54a2a2c0800c0"
+ ],
+ "l2ToL1MsgHashes": [],
+ "timestamp": 1690062623,
+ "rootHash": "0x0898f77ca827e6a3f6edc35c2d8a7ee7243739441dde6ce6db590b12f660601d",
+ "fromAddresses": "0x46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81be4edb277e41dc89ab076a1f049f4a3efa700bce880c67432656d59144ceff962e8faf8926599bcf8e4edb277e41dc89ab076a1f049f4a3efa700bce8a550c99253c5ee0136ac37eb610a28dfe2cc093a9dd711b0cb4430f429231e5cb9940dbd1952a36f69421eeb27ca0941bf46e071098172a56b35df972d584a6d566ea2cee64ea18dddbcddacbb7f681f8a0df28f173103e20de05093aa6e27829715c9dbf4c21df1cc8dfca3b064e9b3c9a0f434c3b9039fee99f3fb8744dd05cbbd87104b7f088d194992ae939a7ef73572e9218c7085aca6d894bc237f15e3aba9e8b0150c6e612d9d49ff8d0ddf9aa4160ee9be8c5b0bdfb7b19c08fbad3c085b05da15cb721c1e7bb53c1eefa4513b69fcbfa900b6bce051a77b08a9b4221a84bb39faa7d6fe0f7664efec9511acc5250939ab0e1aaf6680d54a2b2154e59a43871e",
+ "batchReceptionIndices": []
+ }
+ ],
+ "parentStateRootHash": "0x0db5a89c27cebc50b75b532636687ca2662de28a5341c5b3d4bcb2b4f4e0b9e4",
+ "proverVersion": "local",
+ "firstBlockNumber": 33118,
+ "DebugData": {
+ "blocks": [
+ {
+ "txHashes": [
+ "0x33b8ad2d347b76c3c78e5ecfe521c283ccccccd72c5195880dae9d00f5eab980",
+ "0x15d34713dbd2b309937e1f53e88c77ad50c6f68c96ecac2116b8f8b854381239",
+ "0x31347092ee78bd11582f48825b094e668933677bc1cc606cc317a0223741f699",
+ "0x9c68df3eb4bf8ed1d92d84ae53567574d591c8ddce51d8b34aaba6b2ccd16888",
+ "0xd82b87e4c4ee4d3b108c44d05b7b46513515a66a805b12d2f413f1631e5d8ce1",
+ "0xce4ec8617cdd1fba8463bb76f3767e155af6cc63db1557a697c20065693de49a",
+ "0x06e6ef03f7e67dcc60ae4160966bec0b5a058c92e918b44c62888ae0fc2d27d3",
+ "0x34611763b2a2f865b5df2264cdc3c049aae13e87ef13d77e21f4316fdd2c5c2c",
+ "0x7eed17b099e00c62c4d432f273ac2d22339040f94dcb9241968819ed8eec33d5",
+ "0xb7163f67cd7d2172418f5deb86a62c067f3b7bccc36d3ebc636d7982847f19ce",
+ "0xce223cd4c2cf0acc26af1041bcdbac3823fca09e09db5cd43581ea14c52ada2d",
+ "0x44d4ecff38101357ee1b74d088d0a9e3ce2852b737a108c848e121dd17973dc8",
+ "0xd83b699282e9e087e95f8554101c16da3ed1135d89a75374232b31867745a41c",
+ "0x5eab0f03880003241b85e2d68f3d2ba8dfb63cf67bff90b6a0c3e2fc30ba34b7",
+ "0xfa0abaf3bb7ce0afd7b0fd21ab78eb79372dfce8faa1d68f8f8dfd6ec83f8c66",
+ "0xafc599712a9a10f6311baabc78c270946698ee80e2598b8ad48e2fd6ae8aeb49",
+ "0xd310932c47ff9048b28e2d5082ce413504bf53d23425a15f278822b00720c2a8",
+ "0x5eab0f03880003241b85e2d68f3d2ba8dfb63cf67bff90b6a0c3e2fc30ba34b7",
+ "0x020f58d8a04fb1b64567234d18ee4e33e847b2ad6af1d486fc8c912afa82764b",
+ "0xe9135db60ba9cb6b1b3f81de31e8530c63757065d9e562033a90e43182c9c0d4",
+ "0xf9a34e4dd66ae14a2dd4c03c52cef0933e865f5dd3b5b311069afebb4914fc11"
+ ],
+ "hashOfTxHashes": "0x34a0bf05552d21800f3a4346c9fe2071767daa22f0fac5ecba21f0e356ec87aa",
+ "hashOfLogHashes": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
+ "hashOfPositions": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
+ "hashOfFromAddresses": "0x0387605e3468cc9ceecd4d5a9c7282899879523dd6d98418fd6a8aa787fdd4cc",
+ "HashForBlock": "0xbb50f9fb374ec77f1060aeea1fbddf182feee9c2c17664e3ae5dd9b13fdfb38d"
+ },
+ {
+ "txHashes": [
+ "0xe1e39f8c4731df31d1e9acc2178dcf7c410d839a708d42b049dc09870c144b8b",
+ "0xd22c7b46589c2c84168199b2522322983d6841dddb95a2db7b73eebb146d0989",
+ "0x251ff80c257ebc858547507fb7393efdb53be26bf6ff5bff60f301bedf5b94d2",
+ "0xabbd49c232d04197d5ec243326a11e5960da70d8fa7bdacb96b80c79035e59c6",
+ "0xb37b55c44982c87b90fc8c6894598697838f300f254a521efe453141e5c73ada",
+ "0xf8e6cb20fe34af5a9139c815493b1cf368ddb6f08cbe7ac87446e59beb45be2f",
+ "0xa252d9a0d4d5cab3f9fac8c3b2a01493aa217e20485b03a7c4e8434752210f08",
+ "0x2a0dfee0cd5a49395ed751442e1a48df0a98b144da058f631cca548860980da7",
+ "0xb27047284f817bfba3aa35c83a566b285f918b08da193cba0f57230ae653124f",
+ "0xb01d7705691db2c1fa48e2790704542a50b6fce60469626ae2ed21193fc5d9a5",
+ "0x6515e946304e6907a1c85b02938f9f5f896a7966b2479350ba3db075293fcf2c",
+ "0x1860df19170373188821b9e11815d7efb3150d291e6171d0c37fbaf5604e0ed7",
+ "0x2f168be2953e5b030453a8dc00624425a5f5e05fd8036182b2fa584136e47f18",
+ "0x781c200c88b39cf217b8484955e220b950fdd1b49752324ed6ab2952d5085d01",
+ "0x588ce20ef4021ff9242e1e9ff98457a668e3f52fd32a4f96df64cf7352b4e6ad",
+ "0xb85a6b9fce54bd60b07aa31c2edb5aab2e835d34b53f649ec4a650b056981eee",
+ "0x6d10ddd6d9d92e06c1254d7262ba5e34c817c0cacf7d36238502e0d3a918b33b",
+ "0x0ea5515f39e5fd39dc5bf490794f5e23270f7b020727972f3d24ec5e2e3c8e03",
+ "0x9a9fd46d45e2acd5b7f0c19194ddb1262a4de9474b0f662496339a99ad867e32",
+ "0xde8039adb7e0be4f80438e1b3c0a8a6ded538b602e90998083b98d8a04d434f2",
+ "0x1c459d9b02928bbef00cfdc579d4f69cc4152b0f12a49f92873ee579c5b3cca5",
+ "0x3e57e9ac19b84df49d9bcf1c90ac40f4607e63445f5d3e728d76d9b6e749e844",
+ "0x74632667d6f68741930f8f9c40f8a0762865fb9388e03ca70377aff3225e7462",
+ "0x033505da513d29c7f3f8a49ad7ca1e6215be9bf6ec0db3b1e85bce79a41634c7",
+ "0xa3f969c864f09c6d66a4208aa5773ebc018ee175153ec0e95a7d195214f443ec",
+ "0x47a10f787f700cde6dbb742f82b38b60595522a5b37a3e71142a464214d12005",
+ "0xb288d2887509b067194d38e298401648b70f7c6e073aec5035d946f0cc96eee6",
+ "0x55ce7f07dfc65be1997f829cc48c17fd648dda59a20f15b0838973c4acc2b6ec",
+ "0x9638728c2348797024b771a7f11a7f9ef24ac0ef47e0f6c07748b5d4087beca1",
+ "0x96695bab277b9a8df0e26c73dadaa2489f7f32d2faff836c39fd1cada7f53e45",
+ "0x30c3fa2924cea3a4b6971e54e7a9e063d03bfc007c381e98653c6058673fbe64",
+ "0x296b77172fe6ca7c93f3a27cad3469fe328f38a6b7d62bff8eb113c562811648",
+ "0xf4fb520848b590bfe47bcf93e9cbf61f77a3d9a2a46ce63711aef77e1252e845",
+ "0xe0c716e1abe1ced5af4a5d16068bfeefe71b6b4b0dafeb52dca0e4d62437c6c2",
+ "0x5504974a707746524142e4d87a85158b6e0c1669f8f7ca8c79a7b0c4ab736e5c",
+ "0xcceafae6719af922d536578ed8d9d80b6f9a034dc6d7220543edcc3aeb50b112",
+ "0xab5ffedfa4d3aebc9fd5b5711fc1333df8bda6e6c83d8795479006ca669913b9",
+ "0xf3d3fcdaf453a7ce863571db913b9e49e43f23f8bc21f9edc0e7ab08607c5b27",
+ "0x697fae72571c7445c528179adfb08739b50654713bcc21093ad0ed0bc2343415",
+ "0x6e26e4d0ae4d713d0d68b7da5cd53ee352f3aa020887014225a0991a2a047259",
+ "0x4a9b986c3b05b410aa1f304c914b0c89eecc71a8e23c0d235a81b3c57e76112c",
+ "0xd8b85bd548e76cbfb53679485bf4af4bdadea983abdb7c5e3958e945144f53ff",
+ "0xcb4d071107666f474f15da1571a8cba5a15d15b8fd6b4a1b7f1b0ba2f8e2a3f6",
+ "0xdd3d6ea98fdd96698f5f13c59e5cdb51f97d02991735ff7b5bc880af4834cb00",
+ "0x2259ed1a2d2ed8462338da523a4dbd29c38d1662de859ca583337663700834e9",
+ "0x6bfcea9251674921d5bf38e17f4ffd92a0a89d1b54910bee0e9e89ff53f49efd"
+ ],
+ "hashOfTxHashes": "0x23d15228532fbb55efd61aca03c88cf041fe29a5fa7bcf2bffec9384f0e1bfed",
+ "hashOfLogHashes": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
+ "hashOfPositions": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
+ "hashOfFromAddresses": "0xf1c8491fc9e3379cca6f1b00229d29f288eb5cf50bf41e2de97f7dbba03560b1",
+ "HashForBlock": "0x62ccbbbb86ef488aeeefa412012b550d385ac709ca4edd5549dcdd1d02c49e70"
+ },
+ {
+ "txHashes": [
+ "0x79c5454b996125abe3a3f1b2308e12bc1a05fbb18ccbd961d1e9ed5a60e99a3c",
+ "0xb48f595c9d8b0b3b430a3e18ce714b338d24e151444c802af42c160e83e1c023",
+ "0x1d33b875399ea2b977069a7a3af5af502816bfa57370cfde72b40b797e2cdf9e",
+ "0xf91e12a540222c88104dd71c2cb312651d5c6dffaf4a397b9b823ce006dc35f6",
+ "0xe22e54f211a9dc27fa8e306b760b41ee2430c7c5a6844b034275cd64d7f682cd",
+ "0xdecf2c8e07903f8fac04580c68b202b99b24eb56d6c9c34204718055169f35e1",
+ "0xc65db255285d080713ff417da3054e95df469126b79c0b4b1dd394ac17441683",
+ "0x066f6cbd57a6747706d1611bc48116ff088aa12174d9416a6ff8ff2c4afe33ab",
+ "0xbfc927e159210db0b7ee968257c58b94f55a203b9ff8f9cebf79e38d2b50de86",
+ "0xad05ed5e0b6f984e3ba8a022d0bbcd0787c0a4cda53d18d0de158c4bfe43972b",
+ "0xcfd19e2cfeebd293c1e087e61be6f9b632fbeb72e01b75af7827dbe8b041b9e8",
+ "0x56f4dfd08b403881832831981bbe7fcfbcd0e8da08994793457617793e63d597",
+ "0x8e67aea74c01a3ea7d16a6b9b47efb220e6816ba9e6029944925fba4be554306",
+ "0xc1809842c56bb4d49ef49c35c5c2016a3979f2f467f3eece542f814a1b308474",
+ "0x4309dbc805acd30e711c932cdffed396b097f18edb2a27310851e275ced82711",
+ "0xc2518d2b1ab1d703158e013adf992efc9696f5bc3b807cf3abed054a30529652",
+ "0x0c1bb65bdc1c24fa132c37504059fce12e3edfd3b9c5053797756ee25f07628d",
+ "0x0ecea1436ce18b7c66c6d9e0690506fb974393608267ddc64759d80365082843",
+ "0xf3d59eaeaf5f227e11c4e676b7789c0ad33940716e091bc5278075bd69019327",
+ "0x88e39cd7ba151a9f8f8dc5c3eaae99377f444b34d9be70404b19149efa6dfee9",
+ "0xa6768e6b696584e34764d4d853b5506ca3e21f6030ad576b5d40264382a7c634",
+ "0x604a18caa65f2241536c5f28cb8c5d2e1f2d8c686f9788137c4f24576a10cb52",
+ "0x2f5fea3ed9bbb33919946e2ea03719dcc12a1ff7267a84a2ec432f08b8a1723d",
+ "0xd209e9dd0197a7c10cffbb502b7f4af0714a4b50686d306a270f61e4855f6ebf",
+ "0x158f1510fe03b404484a2b8150ab0730ef8825854cc80977d4f8a3ea3fc7b759",
+ "0xef7487da45ff07d1ac218c7f192305b556b0d5b0906e4d940c5c9dadd50e3c8c",
+ "0x372ed95455b0932ce3bf40d6a8a35de722c626c513f3d92f4b774e8c89e119be",
+ "0x04b8ca4b2734f82a5327a905be3828d8689772d4104222b3f7f15eac2e41a868",
+ "0x2c15d3cc010c7cf663e30f34034743082d5dd002e5978c1f4781c4ff74cad8bd",
+ "0xde23d5166a4751afd2f80792929b9a699d9583d1c3ab84f327eeb09fbb720779",
+ "0xaa58f51031042cb889097716b22be59129f72504d5b9d6633e97a30ed5598c51",
+ "0xc74f177217d41c5b70ec6a523f277cf91a331a0450aac5b7dbc4ae18332c4652",
+ "0x67c43525a6d0d8b45c9c85f1ff260059338a0ad6ad64d456ca9dbfdecddff06f",
+ "0x49d8163a4f3f04ef21cf35fe7a82384ad926fcea2dcfe2a9f01825cc0fa1be2e",
+ "0xe8986d3c28f95a68a57ab7612a6dc801ce17c615cc9ca3dfce9ab6ab28e647ee",
+ "0x6e24f6034ffe2a99719606173f177f119a848f959976fba77d19c5d20523b196",
+ "0x3dd9defe080c662bece7e824f5af8cc6cebcb5d30c1023ea699f2021d2997c10",
+ "0x2f4b77b8a052f4b2fe9233104d6f51bb0c312f524bf315eac8c1544448f13340",
+ "0x7851ce66a67cef5c273839c5c4eaf2a4a1ff58e86c7b9342aa9743e4c462b079",
+ "0x6839a1b38d40df547c006156e5e82f03be4f3e3bfd9fa744cee1c3ec6c6c0b15",
+ "0x271c0dab45c8118a83fb964403263ca7fc4ab44ac86155732b6f66fa08233e0e",
+ "0xcfe46e3f690749455af944d0e9dd3195056898852da82c1298ba63bdf907a37e",
+ "0xb771169740aa9d7d2ed14d10d6b511c960135c877a0eb47cb41d557828268875",
+ "0xc120090ac715d0c88ed0652ca3c81727ebeed0a98b546c71db314decadd0a26d",
+ "0xd452e7c1936098aa268625f147892d1fb3e8e06ba4b9ab2b91a9950644f966c6",
+ "0x27e29d73fa87e9a21a2c2270031edadfeceb2f77392e2dc86023f814775a872c",
+ "0xd499a99d846b7012f01b53a47b8fd1701d89585c59179ea57a19f1de36b81e20",
+ "0x695bea22f78cc9fb72a191911ca33bf1183c7f98e9e34cb2a7e905cb75407c0f",
+ "0x265fabb3192edf158edf7074d30645e4dd1875c77c5d086089feb20622f5b88b",
+ "0x96e46b4eff69253f37399723307a09b3c9de0979db5c48d63e80bc418a57b5bd",
+ "0x48e0a7cfbf088dd37b003dab637c7431ac817220a4d0f9228d6371571e8281ca"
+ ],
+ "hashOfTxHashes": "0xd6407d1b3333a2efc5d37194c55eea9cb8e8fad53fbfd7d009ea3d5019c75128",
+ "hashOfLogHashes": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
+ "hashOfPositions": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
+ "hashOfFromAddresses": "0xf9f76121ce60580d7997cec266645cb12da15caf882ff0075829c2b7243ece91",
+ "HashForBlock": "0xf0e25e3c06a11aa02737a878098dbd5b246c46f29b4f45244c14024e8b22931f"
+ }
+ ],
+ "hashForAllBlocks": "0x0b7ef7c824a8ea7f49231802e8bfcb992f756fdd48e086c8f992f63caf1cedca",
+ "hashOfRootHashes": "0x7dcc64e8c3e428328d3f95778f7340d5fe39015147dc430bdbfb0901892edb6f",
+ "timestampHashes": "0x4662757f920ee71ddf31fa1a88c04f98fb63ef67a3032bc34c12a3ddacba363e",
+ "finalHash": "0x3041a1027bacc392df45fe2e0a35293974440621ecf1efca11e5359819869829"
+ }
+}
\ No newline at end of file
diff --git a/contracts/test/testData/Full/rollup-1.json b/contracts/test/testData/Full/rollup-1.json
new file mode 100644
index 000000000..dd713d9b4
--- /dev/null
+++ b/contracts/test/testData/Full/rollup-1.json
@@ -0,0 +1,308 @@
+{
+ "proof": "0x23821aca8b8a72a2a672434960684640782865bde1d2a6e9f238a2b9cf4c16e203fd6a2b35336aaabc373367d14ec67e82460e9eb2008699f7e055f43b3718132179526db856e99b57ab0d38e1d69439e399ac5bbdf766c36c756575f8dc34c618f3606f7f79487c8bc55a1817eb4b04c679d241b9f98b86ab6364fe6d00318e08017a7a933de03ee950f3fbe5840004d2ce7f9c48d667ecebaad9cd4cc4f47f14924655d581db624b02f3fa1b2022c53c23dfff612eaa4458a38f23bb5a58da125dbaa7b4053189f3c800c28641288b0a7c75d04591c99a1b0666b27db7518d18ff1d42273551e0edc1d73369aa420e83ee905ba1446022c7a75b82973497222a111728c313484f6a2f726950af07850216a09b912c94085d2da660646c5f4908b8927c36ba13dc60ae28d071e9c604fadb66dbaf4979cc447e074c328f278e1cfc649e531783b23c2f469d1e90d58888fe9bb3b65b806f2632eb5aca2f43e716d734fdb03ba8867997c478c5cb72418643a1bb4eeae181c4692dc77b01882f007adb191885268d29282ba206121ba54ec5ff1281195b1aa3894e2bfa3ca084173105c58f474d672ad59b39e1e601e9651627a778417a6f1dcfe7c8342211f221361a8ed2f748f7788e749dfa67d7131af40f2d5fbb4a35c01067b59c82e0740bc73c321863e4cb34122de56506d1b90baa3227776b92f788edb61f0d828a2317db3fd6ad6a6bd30425a86c0c4f80b64b21fd695511bc612f8bdc479adfa92314f52136bde78a5adec621ad9b61ec7920e18de55d068989259fefa8d55dc97d1e37120849dbbc4440b81e335b897e977349cf0e055ce36495132be24bfe45da1db587822115120fc15572ce3e0723e1d90abc940a873bb83c4d64a481f331d8075f452867978605a93b282ad4e5a94b339e5203070d192ab7dac31312048cba13c44edfea3fc4ec9b958d855b6a3a494f1c5b1832678fce81856aed4265b27627c2d582bd3c8ce87212cefb03893db38388030fca44cdbb4d43c98c2d731633241ff322819cb59e9b36c09eeb9a8f043c76db4a7b924a288c137d6b8ce2a0592c69548a780e32eff92812c87b1ea92777050064fdd83ca7f832d61ccec422002567d8e41b950cd07c211ba711f1022a799f4277af8bb4ddff625d5cacc6c00210540550aa0aaf3a4bf4d27d528814d0673b6a9cf0614562a5cb020a2aeb8b281dcccfb2a3c8a85652cb4cd96db617c001edf3fde0b79489ac8712fbefa7c7f8214459c97cdddc6ef1ea85cb7bc587c309983fa4c7b8c5dc410ee3a27f1c4d6b",
+ "proverMode": "full",
+ "verifierIndex": 1,
+ "blocksData": [
+ {
+ "rlpEncodedTransactions": [
+ "0x02f9091382e70882054484962fc6ee85174876e800832625a094508ca82df566dcd1b0de8296e70a96332cd644ec80b908e4f4b476e100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000045161d5e6ce5e92df9502203fd8e60b26cb70894d99155576d7776057cf189577b3f762c86d9b7dcb0ccf3be280ac541f2d0514fcea664dadd7456cf50eaf2d2f10013acd8b6ab926aebce6479e1e69cba954d9a12280333a01764665a5753f2e465feaf9735a1a209af27b3961e604f09a79ed9ddc8cba4fd3f32446a2018bdbc0f876fee16158abb8ee62791d73979460eae03f4463c687662eb4ed55366f286fef0cc22ced3e884f6ada00b47ab4a643bbac88289e65a41ee741307eda80be8e8964030c3a8d5d5c81f99999a0b6d68a7b3af3a9a077c1d1cb7a61f0d1199089baf45c5dd81d359e4744b02f811ff6826d0db21c23686440c3c4019023691a394a7655f567ed5441ff236f1bd06db8fe26c9ceada60fd7f4b2180ad0db7833092bf2c52a1d0b4fe82a78bded7ee559ffb37f74de252e169ef907faa79cc84e888b947aad4534065024a5c3932e1ffa287fda0890fae04b45c2913c31fd992322973f916e0feaeee2180645f59b3336a07d7fdb2db52fdde27ccc544e2b030efa3db5e99efaab00e74314333b92bf5c358a61fd7e4ff920e8cf903866615adeaf2df544df269404bba4d4f94e87fc0f33d980511b4f3677eb337784bfb50209ae5f9fc6642c89a99698aec9767a936745e5538815221121ff20e1b6c89deeb292ea4eb17dbdd045924d3afcd4c418cf4f6a0a10c7231bbf852f2f4e18393f13cef6772e7e57ba3c74a3df64ae759e47be96b1185898570ce963d9c67acc30cfcb5ee5930478f657324e1427c76e49afd25111e0c9eec6842334ed75d0c5ba1c465342c6e75e0c5439f2fc0ea9761fe2b6890247f549ca4167425a953e642c31c9159b2d892edbee614e439766a7ddd8a66cd25bf9a45d2d1f580cf91c77386c1df74a4d98d38af559dc53177503e9157c6df8c8889d7f47bcee62749efb5b0614cce452e428e4780faad5ab385f77ecb89686c1c026927a9607bcd368c1212b0ee1dae5b27bd4796eb07b7cbb65accda47b8fc2336d53611ba5a880e3a84b41a65a9ff6e91c0f8ee8e00b506e81a8eacefc803d9474edbe0380898f324d49ea68fa2c7b2f5d52ee5e47308a0b2788937bb1195f5603845b07eeaad81b6d65f31028e614b18f73872399f6b643c8e33eb88eabdce099ff3eacd42193ea732a22826c6e79c13acb66deed9ca11ce95ffcd356a25e4f3e4b60f7fa36cd40d7171589fe695da70d6c976a4d79a84137713bd63eed6e5622f351faee3169cbb59c327d02471a720e7648c4c47a6592cc5d7c148fefbfeafd91d8ad350e14c691680e8fd47baf17f82b0aab2c3dbd23c76434c7b4e07c1e1c21a98886e2c6e866b8a60af2c75df6e29e0b40000070c6710f29e1b4e0c2e18cb2cf68344c8e259ee85408585f12b4ab7316f36b3c31fd1097faa1a036b6f1a74015420b3eca68daa03002ab06d810efce532fcd0996960288a1be9a2a43ac828b3ab4538853860a9bfebd8b7c0f65cdc54138137aed477733cc9726b748679d13f516150ccf39410795f32517bfbec0f3ed4dcfe2ef70029e35cacba809bbbc2a6c82543b19c1c1bff7fab52327ac00465b35015d9baaaec5a5aba682f9bef177e216e9e77319aadf64d7790878c01cef76b23eba1ed55f16ecfea6decde45118d2efbe4c22fb630d1c27742f4723148a934c1a97454fbf3fd603ef5a3f7d38ce4ae87bad4c25523afd669a112750bfaebf85f2e465cfbfb3bf8bad1b558ea91c556145eb87ec02f984d8c2f44369c14bf265536a5f32d36711add8c3ae841ce268b0c52533c64e3d841c9d4b63658f8e0ad32bb512aa7fb77f3baf72ff71bfe214676fa5650eae2971d0ce43b49abf645430874e5fc9dccf668217040fdf3cad9ab0140915c65fd5e9816743a249984e91746c5f0670d7fad7d642158142d570a7ec9e761f37bbc7ea7245befdeda160dc55ef1e2512a16ef5a3668ad785acf4fa836902aeb07ae03ceb24267beda32f3cfb7f5a9929434a3a9f6ddf7e4cfaecf5b071578c047d52a857dd5b30077d6a874892558f8798ee1c335aec8c8e47918ebec1f2a6c1cedc7b4d8185fc041defbef038d3b379ad7e84a047c132a49d0a28e86adf4d11d50da3a8875e89291558805f75e43c02645f08c8444184b87ab93d644b0051e7a6a756a059a97e91a82349d8907a33c1c90daa6e66deefb448b2cde8fe121bfc8b582c556527a7bfb11fde9b4bc645ad35369d630fe3593e020630231687d04cc5c81317eee0976e63f63644f20dd993203ec0ebf3be9af1b3a2d9ee3a779202d69d3d27b7aed886479604731caea8e3c8d878f030acb22a92fde9856ca342222759a9ad734e4a280c23573a003230f1c515e66c531beb518c2c4786d64c46c49854c2fd17722e1a82c08b3a35bc59f7771f29c4bf4e6a18ddebb66beac48ad7bda9122be176ded174b963bb4f3b8b13efdb9bef4608b9f3fdec2222b683dfa6bc41f247f76b2644dfe71e95f5b0b46aee8350e95a3afb46262a20d0e99e9216a317a571a319018d134ef83c9e335af1e46d0eae1377320c8036394d7218341eb4c775a97c2ca8817d1d0febd1e48604e33b395a13a9a36f1151fab3b1470bcfd924e6442ddc8cb7284bb159abef1d2ce5f23c3d892eb74bd31f02a8754aaf9d07476724c70349a8fd5d8d370579461052a9aae0072e79b5fb5a8b5c0f0ee747747554bb1608f7670b9e14b680ce360caa6097ccee08710816b325ac9d18038eab16b16de7a4d727f9dedb9a145a39952e31b264c136bd46d8b45f8683a2af9f27006325ad9e342bafb6ac4756485a79d8cf2d63d72a2284d1118bd195098891bc23831b605b47a9c8665d9bb7b7eb7b5966bc37a56c53f6fcbe28953add2b9fb7d6bf7d9d480563e0cd07f05226c9c8a3c3496cebed0000aced1c27ad9e3494362cd6da3d8686dde841fcc724d830e71160c0359393d089ae3e49f412b567823e5efdb4f43d246e35b2e73620a8b0d6c3f5f45709180c6ebeb48211c422f65a5374b5914b0d3fa1ca03f1960c7a50b6ddba710de8859c132e48e57c57485d937c11c4648fcb2b8a636f3b2a1e27ced0e081c57e8e3f50b5c78481fe743daafd2d15cdfc0",
+ "0x02f582e708823ed185012a05f200852e90edd0008252089452578e1fe11f92618dd178629156de2307b1065d880234e1a85749806180c0",
+ "0x02f86e82e70813846ba55515846ba55515827223947d43aabc515c356145049227cee54b608342c0ad80b844095ea7b300000000000000000000000080e38291e06339d10aab483c65695d004dbd5c690000000000000000000000000000000000000000000000009275143d32c82d5dc0",
+ "0x02f84d82e70808846ba55515846ba55515828cc794e5d7c2a44ffddf6b295a15c148167daaaf5cf34f80a42e1a7d4d00000000000000000000000000000000000000000000000001866c3674ca28e7c0",
+ "0x02f9029682e70801846ba55515846ba55515830282209480e38291e06339d10aab483c65695d004dbd5c69861b48eb57e000b902642cc4081e000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000fcb9d92cf386180000000000000000000000000000000000000000000000000000000064bc7dcb000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001b48eb57e0000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000046ec4bb184528c3aee6f1419e11b28a97f33d4830000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f000000000000000000000000126824925a67f98dca1eb9d92d78d51c99f3a42700000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f901d082e70806846548b1a4846548b1ae8307c07494438670d41d5118003b2f42cc0466fbadd760dbf480b901a4ea5406320000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f000000000000000000000000000000000000000000000000000000000000012cfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed928fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed9dcfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed8ecfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed9a000000000000000000000000000000000000000000000000093a7cafa8b08e3b30000000000000000000000000000000000000000000000000011c37937e0800000000000000000000000000000000000000000000000000046823c1fd558d2f60000000000000000000000000000000000000000000000000007351f791fb3be000000000000000000000000a8a71d5fb2d5fbc8f31799c5cdd8ae67722885a50000000000000000000000000000000000000000000000000000000064bc5393c0",
+ "0x02f86f82e70809846548b1a4846548b1ae8301042f947d43aabc515c356145049227cee54b608342c0ad80b844095ea7b3000000000000000000000000438670d41d5118003b2f42cc0466fbadd760dbf40000000000000000000000000000000000000000000000047049d62f86d62000c0",
+ "0x02f8cf82e7080b846548b1a4846548b1ae8303374994b29caa2cb1feb7f4ccaa9dd9b8ad2022eaca6ec380b8a46170b162000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000413f019ead393d9d0c66745b08e818fe13179bd3da2d6559b2bb9c40aca9618c364ecece8c8fc0d89d0e5897210ae3b5521f49cbb178e00ee568be62d583edceb51b00000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013082e7080e846548b1a4846548b1ae8302cdc594c66149996d0263c0b42d3bc05e50db88658106ce80b9010418cbafe500000000000000000000000000000000000000000000000098a7d9b8314c0000000000000000000000000000000000000000000000000000001420824645e5d100000000000000000000000000000000000000000000000000000000000000a000000000000000000000000078ea6f35af55843f9d1d654f4a076ef435fa34690000000000000000000000000000000000000000000000000000000064bc539f00000000000000000000000000000000000000000000000000000000000000020000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34fc0",
+ "0x02f9013082e7080e846548b1a4846548b1ae8302cdc594c66149996d0263c0b42d3bc05e50db88658106ce80b9010418cbafe5000000000000000000000000000000000000000000000000a688906bd8b000000000000000000000000000000000000000000000000000000015f4b85f94d69f00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000005029056a3581629953feba7cba2446b6057295160000000000000000000000000000000000000000000000000000000064bc53a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34fc0",
+ "0x02f9013082e7080e846548b1a4846548b1ae8302cdc594c66149996d0263c0b42d3bc05e50db88658106ce80b9010418cbafe50000000000000000000000000000000000000000000000009cd1aa2149ea00000000000000000000000000000000000000000000000000000014acf9cb1dc31100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000326b931b50a190704341c4f832ebd7a75f4e86290000000000000000000000000000000000000000000000000000000064bc539f00000000000000000000000000000000000000000000000000000000000000020000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34fc0",
+ "0x02f9013082e7080e846548b1a4846548b1ae8302cdc594c66149996d0263c0b42d3bc05e50db88658106ce80b9010418cbafe50000000000000000000000000000000000000000000000009f98351204fe000000000000000000000000000000000000000000000000000000150a9e61f0246600000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000d7c0fb08b29ea8bbca31b51d6d7bebadf288e2af0000000000000000000000000000000000000000000000000000000064bc539f00000000000000000000000000000000000000000000000000000000000000020000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34fc0",
+ "0x02f9023782e70816846548b1a4846548b1ae83054dfe94438670d41d5118003b2f42cc0466fbadd760dbf4871d2b46f19c59e6b90204ac9650d800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000104c238a3a30000000000000000000000000000000000000000000000000000000000000fcbfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeccf8fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed0e0000000000000000000000000000000000000000000000001f9129260ed919f98000000000000000000000000000000000000000000000000001d2b46f19c59e6000000000000000000000000000000000000000000000001d1781783df86e0cf000000000000000000000000000000000000000000000000001902f4b0f0b9cf0000000000000000000000000000000000000000000000000000000064bc53930000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041faa413300000000000000000000000000000000000000000000000000000000c0",
+ "0x02ed82e70880846548b1a4846548b1ae82b22494cce9d3f392c135dc038b147ca73ec496f7f89d938084183ff085c0",
+ "0x02f86e82e70803846548b1a4846548b1ae82fb2a9466627f389ae46d881773b7131139b2411980e09e80b844095ea7b3000000000000000000000000438670d41d5118003b2f42cc0466fbadd760dbf4000000000000000000000000000000000000000000000000000000000046b7f0c0",
+ "0x02f9013082e7080f846548b1a4846548b1ae8302cdc594c66149996d0263c0b42d3bc05e50db88658106ce80b9010418cbafe50000000000000000000000000000000000000000000000009f98351204fe000000000000000000000000000000000000000000000000000000150a9e61f0246600000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000cc6fc47b6a3a03b41f43fd33c699a1ac32e3b6910000000000000000000000000000000000000000000000000000000064bc53a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34fc0",
+ "0x02f9013082e7080d846548b1a4846548b1ae8302cdc594c66149996d0263c0b42d3bc05e50db88658106ce80b9010418cbafe5000000000000000000000000000000000000000000000000a25ec002c012000000000000000000000000000000000000000000000000000000156842a209508f00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000002183ba8662f9f0e1bebffab11e31ba8cae53bcec0000000000000000000000000000000000000000000000000000000064bc539f00000000000000000000000000000000000000000000000000000000000000020000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34fc0",
+ "0x02ed82e70880846548b1a4846548b1ae82b22494cce9d3f392c135dc038b147ca73ec496f7f89d938084183ff085c0",
+ "0x02f86e82e7080b846548b1a4846548b1ae82b7679466627f389ae46d881773b7131139b2411980e09e80b844095ea7b3000000000000000000000000272e156df8da513c69cb41cc7a99185d53f926bb000000000000000000000000000000000000000000000000000000001ee98853c0",
+ "0x02f86f82e7080b846548b1a4846548b1ae8301042f947d43aabc515c356145049227cee54b608342c0ad80b844095ea7b3000000000000000000000000438670d41d5118003b2f42cc0466fbadd760dbf4ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0",
+ "0x02f082e7080f84623ed54584623ed54f8252089445a318273749d6eb00f5f6ca3bc7cd3de26d642a87b8bdb97852000280c0"
+ ],
+ "l2ToL1MsgHashes": [],
+ "timestamp": 1690062599,
+ "rootHash": "0x290c378f1d39464c72c71773d84030cbf3d354bca03f92caae4139a06876790f",
+ "fromAddresses": "0xc1c6b09d1eb6fca0ff3ca11027e5bc4aedb47f6780c67432656d59144ceff962e8faf8926599bcf86810dba12317406828d64b07d34ad22338644bb3c4f53c1aeb4e8f12b2c427e7a4505cd189be3392126824925a67f98dca1eb9d92d78d51c99f3a427a8a71d5fb2d5fbc8f31799c5cdd8ae67722885a552e085691b4ae78da51187a5e9d8381f08fe2bd392b1a1c7c7542e23a65e528d2d58a891c9aa475978ea6f35af55843f9d1d654f4a076ef435fa34695029056a3581629953feba7cba2446b605729516326b931b50a190704341c4f832ebd7a75f4e8629d7c0fb08b29ea8bbca31b51d6d7bebadf288e2af4c9728684fd37254a82956ad9a8a87bbd938364a7a145a31686980565976eae84024d4ca9c06bda82d584a6d566ea2cee64ea18dddbcddacbb7f681fcc6fc47b6a3a03b41f43fd33c699a1ac32e3b6912183ba8662f9f0e1bebffab11e31ba8cae53bcec359084e2583e835807164c0f864ecff655d1849f4de89c2ae04a31cc2dba3e2da11a38ddc43682b90ff2e6c1d14ce8a94e74d0eb9d6f2536363aeb409232224978f9b83b580ba028bdab92d7976ab41f",
+ "batchReceptionIndices": []
+ },
+ {
+ "rlpEncodedTransactions": [
+ "0x02f9013282e70882b240846d0182b0846d0182be8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000090ae52f244688d687b693d99769e455a38de9b2b00000000000000000000000090ae52f244688d687b693d99769e455a38de9b2b00000000000000000000000000000000000000000000000000013811a83d9bc0000000000000000000000000000000000000000000000000002aa1efb94e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd090000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b241846d0182b0846d0182be8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000052df1b28c2afc2cbb0b320fd787a7844c6e580cd00000000000000000000000052df1b28c2afc2cbb0b320fd787a7844c6e580cd00000000000000000000000000000000000000000000000000013811a83d9bc0000000000000000000000000000000000000000000000000002aa1efb94e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd0a0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b242846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000000bda323c78a069cee9d2265ced3bcfa1332ba7070000000000000000000000000bda323c78a069cee9d2265ced3bcfa1332ba7070000000000000000000000000000000000000000000000000002e50893ba5a59000000000000000000000000000000000000000000000000001b0d7ea876e025000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd0b0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b243846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000b9652a464ee262d5aedc9189fcb59ea04441b21a000000000000000000000000b9652a464ee262d5aedc9189fcb59ea04441b21a00000000000000000000000000000000000000000000000000031b636ca3611e000000000000000000000000000000000000000000000000001a66ab71d3c9ad000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd0c0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b244846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000d1a6dc9b8762fe8c93c5ac103a1e365cc9fb5a06000000000000000000000000d1a6dc9b8762fe8c93c5ac103a1e365cc9fb5a060000000000000000000000000000000000000000000000000003391f90d6498f000000000000000000000000000000000000000000000000001ab250d3446d10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd0e0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b245846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000a9b13af6181ff98c9eeaf9ca46095ee2f916f832000000000000000000000000a9b13af6181ff98c9eeaf9ca46095ee2f916f83200000000000000000000000000000000000000000000000000028c843d42655a000000000000000000000000000000000000000000000000001a62ddacb258dd000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd0d0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b246846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000004acc7828d76abf4f84105f7c826a0d52b00ba38a0000000000000000000000004acc7828d76abf4f84105f7c826a0d52b00ba38a0000000000000000000000000000000000000000000000000002827a4d2c36e8000000000000000000000000000000000000000000000000001b5434999b29e6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd100000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b247846d0182b0846d0182be8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000027bc8621471d8d84475a211fb7556c158958d0d200000000000000000000000027bc8621471d8d84475a211fb7556c158958d0d20000000000000000000000000000000000000000000000000001429fc0610640000000000000000000000000000000000000000000000000001c110215b9c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd110000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b248846d0182b0846d0182be8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000007e4e0c3be87aef0e846162c40d96c7ffbcf1ca130000000000000000000000007e4e0c3be87aef0e846162c40d96c7ffbcf1ca1300000000000000000000000000000000000000000000000000013811a83d9bc000000000000000000000000000000000000000000000000000f8b0a10e470000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd130000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b249846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000ded87809191d1ce529b875d81478f08c4e7e1028000000000000000000000000ded87809191d1ce529b875d81478f08c4e7e10280000000000000000000000000000000000000000000000000001967d1439851800000000000000000000000000000000000000000000000000523f1929090ebc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd120000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b24a846d0182b0846d0182be8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000004f579b878eebf064b1d1f1d8b8032f869ec650190000000000000000000000004f579b878eebf064b1d1f1d8b8032f869ec6501900000000000000000000000000000000000000000000000000013e2428d4b4e00000000000000000000000000000000000000000000000000017cd9d4ffec000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd140000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b24b846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000e512e419213e04728cf87c784620cd1bb0e91fdc000000000000000000000000e512e419213e04728cf87c784620cd1bb0e91fdc00000000000000000000000000000000000000000000000000025ab9cef6d3b6000000000000000000000000000000000000000000000000001c3ef9184c62bb000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd190000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b24c846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000004667317607f05721a60b69e2da3254b58b4fa2150000000000000000000000004667317607f05721a60b69e2da3254b58b4fa21500000000000000000000000000000000000000000000000000027612ae8381aa000000000000000000000000000000000000000000000000001b3ff4d6abb3dc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd170000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b24d846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000a91a200fd2959c0a3bb530eb6e14c5376fadcce3000000000000000000000000a91a200fd2959c0a3bb530eb6e14c5376fadcce300000000000000000000000000000000000000000000000000024479a76f5533000000000000000000000000000000000000000000000000001aedb721196262000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd180000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b24e846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000a9b49485282fe1d16a1e7dffab0378fd2922e27f000000000000000000000000a9b49485282fe1d16a1e7dffab0378fd2922e27f000000000000000000000000000000000000000000000000000233e4626489ee000000000000000000000000000000000000000000000000001b36c2a7ac1d62000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd150000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b24f846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000059ad5ae25a4c2e40ef2fb5df27db491af72d6d4200000000000000000000000059ad5ae25a4c2e40ef2fb5df27db491af72d6d420000000000000000000000000000000000000000000000000002b7ebd3b6c5f3000000000000000000000000000000000000000000000000001c6d36103d6026000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd160000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b250846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000c754a76208d323fabfb5e60b5cd6602673236bb3000000000000000000000000c754a76208d323fabfb5e60b5cd6602673236bb300000000000000000000000000000000000000000000000000022fbb5512f782000000000000000000000000000000000000000000000000001b7679a96d2844000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd1a0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b251846d0182b0846d0182be8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000001ed2d8eebcd1d0319a4e5374bdbed288589f00a10000000000000000000000001ed2d8eebcd1d0319a4e5374bdbed288589f00a10000000000000000000000000000000000000000000000000002691003e5e8d6000000000000000000000000000000000000000000000000001b10359ee7ad8f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd1e0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b252846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000dfa435f8df3036bab2e1ffe4923840178ea54c3e000000000000000000000000dfa435f8df3036bab2e1ffe4923840178ea54c3e0000000000000000000000000000000000000000000000000002a991e6a46a9a000000000000000000000000000000000000000000000000001bff45bf1da75e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd1d0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b253846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000feffbb43619cad27dc96a60618495be1a8882f5b000000000000000000000000feffbb43619cad27dc96a60618495be1a8882f5b00000000000000000000000000000000000000000000000000026a801a888074000000000000000000000000000000000000000000000000001b103c21e6c968000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd1b0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b254846d0182b0846d0182be8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000e6f9e9783211e6d22108bf30d448e27694479d38000000000000000000000000e6f9e9783211e6d22108bf30d448e27694479d3800000000000000000000000000000000000000000000000000016b3f8f9ae760000000000000000000000000000000000000000000000000001cc6e836ae4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd1c0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b255846d0182b0846d0182be8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000dae2f9005c7c2bb2a29bf4eee6ee8c29cc3af7ff000000000000000000000000dae2f9005c7c2bb2a29bf4eee6ee8c29cc3af7ff000000000000000000000000000000000000000000000000000237fe04de55fc000000000000000000000000000000000000000000000000001bf2081e5ca582000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd1f0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b256846d0182b0846d0182be8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000b25cb5c1c5ac5b59971e907ed97e90364b760e30000000000000000000000000b25cb5c1c5ac5b59971e907ed97e90364b760e3000000000000000000000000000000000000000000000000000016b3f8f9ae760000000000000000000000000000000000000000000000000001c3e7b9df6e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd200000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b257846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000004d58159ed0e1fa7992970875e7860bb9755d4e040000000000000000000000004d58159ed0e1fa7992970875e7860bb9755d4e04000000000000000000000000000000000000000000000000000228d889a3d34a000000000000000000000000000000000000000000000000001b3c16d950ce97000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd210000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b258846d0182b0846d0182be8301361d94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000ee99f3fb8744dd05cbbd87104b7f088d194992ae000000000000000000000000ee99f3fb8744dd05cbbd87104b7f088d194992ae00000000000000000000000000000000000000000000000000016b3f8f9ae760000000000000000000000000000000000000000000000000016345785d8a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd230000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b259846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000076d696f8f3f5904aecf0f8982b82f88d80228f6700000000000000000000000076d696f8f3f5904aecf0f8982b82f88d80228f6700000000000000000000000000000000000000000000000000029a2f0df23dd9000000000000000000000000000000000000000000000000001c63e6326e3509000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd220000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b25a846d0182b0846d0182be8301361194508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000f2ed2042f3bf1c9e9ec3de5a79d6b8334e498d20000000000000000000000000f2ed2042f3bf1c9e9ec3de5a79d6b8334e498d2000000000000000000000000000000000000000000000000000016b3f8f9ae76000000000000000000000000000000000000000000000000000470de4df820000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd250000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b25b846d0182b0846d0182be8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000006bd99000b5874c02e666ced80905f038024296900000000000000000000000006bd99000b5874c02e666ced80905f03802429690000000000000000000000000000000000000000000000000000299cc8b064176000000000000000000000000000000000000000000000000001a7abad9e76168000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd240000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b25c846d0182b0846d0182be8301993a94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000008a8625919bfa16c58f5c3aa900c616be91955c520000000000000000000000008a8625919bfa16c58f5c3aa900c616be91955c5200000000000000000000000000000000000000000000000000016b3f8f9ae760000000000000000000000000000000000000000000000000001ff973cafa8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd270000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b25d846d0182b0846d0182be8301361194508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000000a6f2f5ad8954edbadfdb508e599cd26ab668b970000000000000000000000000a6f2f5ad8954edbadfdb508e599cd26ab668b97000000000000000000000000000000000000000000000000000155aa30e6ad80000000000000000000000000000000000000000000000000002386f26fc10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd290000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b25e846d0182b0846d0182be8301361d94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000c1ea3db638495d90bb778863ff5c2aaccfbbf421000000000000000000000000c1ea3db638495d90bb778863ff5c2aaccfbbf42100000000000000000000000000000000000000000000000000016b3f8f9ae76000000000000000000000000000000000000000000000000000c3663566a58000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd2a0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f482e708823ed285012a05f200852e90edd000825208949d0e0c96288a3ebdda1bd73523aeb8e262c6e679872714711487800580c0",
+ "0x02f482e70882402185012a05f200852e90edd0008252089468ad1fa00cb9d499b73e85c6449766374463b6b2872386f26fc1080980c0",
+ "0x02f482e708823ed385012a05f200852e90edd00082520894fa8d660cfa214e3071b915d5bb88c75834e9a15587470de4df82005080c0",
+ "0x02f84d82e70808846ba55515846ba55515828cbb94e5d7c2a44ffddf6b295a15c148167daaaf5cf34f80a42e1a7d4d000000000000000000000000000000000000000000000000000d51e75245751bc0",
+ "0x02f86e82e7080b846ba55515846ba5551582b4fb947d43aabc515c356145049227cee54b608342c0ad80b844095ea7b300000000000000000000000080e38291e06339d10aab483c65695d004dbd5c690000000000000000000000000000000000000000000000014546d1a4322aca3ac0",
+ "0x02f9029782e70801846ba55515846ba5551583023e759480e38291e06339d10aab483c65695d004dbd5c69874c1522a7a7ae9cb902642cc4081e000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000224ec2fcd3bcafda90000000000000000000000000000000000000000000000000000000064bc7ded0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004c1522a7a7ae9c000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000007f72e0d8e9abf9133a92322b8b50bd8e0f9dcfcb0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f000000000000000000000000de705471bb4414bfdabe09f52665914ed0e5725400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f86e82e7080e846548b1a4846548b1ae82b3e494e5d7c2a44ffddf6b295a15c148167daaaf5cf34f80b844095ea7b3000000000000000000000000272e156df8da513c69cb41cc7a99185d53f926bb000000000000000000000000000000000000000000000000001a711930b89000c0",
+ "0x02f86e82e70838846548b1a4846548b1ae82b4fb947d43aabc515c356145049227cee54b608342c0ad80b844095ea7b300000000000000000000000080e38291e06339d10aab483c65695d004dbd5c6900000000000000000000000000000000000000000000000c31f1cf83c3d8fb3ec0",
+ "0x02f482e7080a84623ed54584623ed54f83014cab94a02573c4ad15c16b48f10842aac9c9ea405b65a386d12f0c4c6000841249c58bc0",
+ "0x02f582e7080184623ed54584623ed54f83012779943c5b31b158dcaba76df46bd853c8af3ccf0f002287016bcc41e90000841249c58bc0",
+ "0x02f9027082e7080784623ed54584623ed54f8302d78c94272e156df8da513c69cb41cc7a99185d53f926bb80b90244ac9650d800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000104a8c9ed670000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f000000000000000000000000000000000000000000000000000000000000012c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064bc53ab0000000000000000000000000000000000000000000000001f9a0ef077f3ff15000000000000000000000000000000000000000000000000000444a1b61145790000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044bac37ef7000000000000000000000000000000000000000000000000000444a1b611457900000000000000000000000017cec49895644f84c50809d874e22feb923553d800000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9023782e7084284623ed54584623ed54f83035d6494272e156df8da513c69cb41cc7a99185d53f926bb8701e794b458ec96b90204ac9650d800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000104bfba6b22000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f0000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000000000000000000000000000000000000000012c000000000000000000000000a6dff484a46f90e5d03ff91e766ac70cf1e0eecb0000000000000000000000000000000000000000000000000000000064bc53870000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000001e794b458ec9600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041faa413300000000000000000000000000000000000000000000000000000000c0",
+ "0x02f901d082e7080484623ed54584623ed54f8307359694438670d41d5118003b2f42cc0466fbadd760dbf480b901a4ea5406320000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f000000000000000000000000000000000000000000000000000000000000012cfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed928fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed9dcfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed8ecfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed9a000000000000000000000000000000000000000000000000032c969648a3f809200000000000000000000000000000000000000000000000000060a24181e40000000000000000000000000000000000000000000000000001864ecab95235e0600000000000000000000000000000000000000000000000000026db13ab8909c00000000000000000000000038022d79892c57761a67ab251c7d70a55dbc87120000000000000000000000000000000000000000000000000000000064bc53abc0",
+ "0x02f8f782e7081084623ed54584623ed54f8302cbbc94c66149996d0263c0b42d3bc05e50db88658106ce8803a2956f887d21acb8c4f305d7190000000000000000000000009201f3b9dfab7c13cd659ac5695d12d605b5f1e600000000000000000000000000000000000000000000004dea22ce324f339a3a00000000000000000000000000000000000000000000004d8667c05d93ecf1c6000000000000000000000000000000000000000000000000039dee49db01a039000000000000000000000000633eb62c0a2a7c6ff9d03f801b1adfa5f02cc55b0000000000000000000000000000000000000000000000000000000064bc538cc0",
+ "0x02f86e82e7081384623ed54584623ed54f82b4fb945471ea8f739dd37e9b81be9c5c77754d8aa953e480b844095ea7b3000000000000000000000000272e156df8da513c69cb41cc7a99185d53f926bb0000000000000000000000000000000000000000000000000a1a9ce0f703af0ac0"
+ ],
+ "l2ToL1MsgHashes": [],
+ "timestamp": 1690062611,
+ "rootHash": "0x1f00ce5b9e15ff5f9cbcba3ab213ca2c22dabecbe5f8a902553aedef133152b3",
+ "fromAddresses": "0x46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b80c67432656d59144ceff962e8faf8926599bcf8e4edb277e41dc89ab076a1f049f4a3efa700bce880c67432656d59144ceff962e8faf8926599bcf827d967c1a5636fcea985f696525bb4f911dd5c59793560c4e66c450545bc54271f84b17d989c5ecbde705471bb4414bfdabe09f52665914ed0e57254d59c3b026e9abef3f744491462d369e4c27040b312e7301eef23a02c5162dce59c0b8ba264dc92e46abc316192b43dc64c79ec841ac9546f793022c420bae152846374d7572c1fcfe182e6bb5cfcb13117cec49895644f84c50809d874e22feb923553d8a6dff484a46f90e5d03ff91e766ac70cf1e0eecb38022d79892c57761a67ab251c7d70a55dbc8712633eb62c0a2a7c6ff9d03f801b1adfa5f02cc55b5636d6f8810951d686a22da016654f22efdda85e",
+ "batchReceptionIndices": []
+ },
+ {
+ "rlpEncodedTransactions": [
+ "0x02f9013282e70882b25f846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000007fbebf89ec31747099485233aacef5e3f39fd5950000000000000000000000007fbebf89ec31747099485233aacef5e3f39fd59500000000000000000000000000000000000000000000000000024523c543d459000000000000000000000000000000000000000000000000001b60f3f6387fe2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd260000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b260846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000a885747c9baade27c1a57d5747ad128e3d069bb5000000000000000000000000a885747c9baade27c1a57d5747ad128e3d069bb50000000000000000000000000000000000000000000000000002858ff455d411000000000000000000000000000000000000000000000000001b7919966d30ba000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd2b0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b261846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000e6dbe9a315aa775a52a749923accbc752665a2db000000000000000000000000e6dbe9a315aa775a52a749923accbc752665a2db0000000000000000000000000000000000000000000000000002a19565cc15f9000000000000000000000000000000000000000000000000001d2b3e94606485000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd2c0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b262846d0182b0846d0182be8301993a94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000001f657297fbe86fb4bf3ebe00d47a7f047cf5e8f40000000000000000000000001f657297fbe86fb4bf3ebe00d47a7f047cf5e8f400000000000000000000000000000000000000000000000000014d8f7e915b2000000000000000000000000000000000000000000000000000205466db74c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd2d0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b263846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000079e8da554811076d8df8975c2175c696c438592b00000000000000000000000079e8da554811076d8df8975c2175c696c438592b00000000000000000000000000000000000000000000000000032e61fb77d53b000000000000000000000000000000000000000000000000001bf05f22026dd8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd2f0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b264846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000084802571e30720a5dde1af48b84915d0a7d9d2a900000000000000000000000084802571e30720a5dde1af48b84915d0a7d9d2a90000000000000000000000000000000000000000000000000002c4c9673ddda2000000000000000000000000000000000000000000000000001b39ac02dbb9c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd2e0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b265846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000007fa3116b528f8941e6e8530e4a36d0a9d80574db0000000000000000000000007fa3116b528f8941e6e8530e4a36d0a9d80574db0000000000000000000000000000000000000000000000000003118f508e1ad3000000000000000000000000000000000000000000000000001ba80bb438c2f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd300000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b266846d0182b0846d0182be8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000a5d3299a38b5d0020a1690f11f8450c9e197d654000000000000000000000000a5d3299a38b5d0020a1690f11f8450c9e197d65400000000000000000000000000000000000000000000000000016b3f8f9ae760000000000000000000000000000000000000000000000000001cc6e836ae4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd330000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b267846d0182b0846d0182be8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000078874c29e4a3de39618e9bc39a239bd008c6472e00000000000000000000000078874c29e4a3de39618e9bc39a239bd008c6472e00000000000000000000000000000000000000000000000000014d8f7e915b20000000000000000000000000000000000000000000000000001d7cce57a2c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd320000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b268846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000570e9127026d8764bf8402fe10bfd7dcbb665ed1000000000000000000000000570e9127026d8764bf8402fe10bfd7dcbb665ed100000000000000000000000000000000000000000000000000028aaaa249f44e000000000000000000000000000000000000000000000000001c0b062dd167a6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd310000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b269846d0182b0846d0182be8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000d33e335e680198a0c141b8ae5769498a000fc998000000000000000000000000d33e335e680198a0c141b8ae5769498a000fc9980000000000000000000000000000000000000000000000000002f55c5d4b2dc2000000000000000000000000000000000000000000000000001be650d1ea042c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd340000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b26a846d0182b0846d0182be8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000004f6b45db2da225dc9dfa29eb258d3d63018c72060000000000000000000000004f6b45db2da225dc9dfa29eb258d3d63018c72060000000000000000000000000000000000000000000000000001429fc0610640000000000000000000000000000000000000000000000000002081e063b1e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd360000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b26b846d0182b0846d0182be8301993a94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000072d0a5f5f802ca3950d9ff8abf467e6b1465d43900000000000000000000000072d0a5f5f802ca3950d9ff8abf467e6b1465d4390000000000000000000000000000000000000000000000000001438dbfe44300000000000000000000000000000000000000000000000000001c6bf526340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd350000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b26c846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000051870c7d99f761b9297433be7256ea24290a0bbd00000000000000000000000051870c7d99f761b9297433be7256ea24290a0bbd00000000000000000000000000000000000000000000000000024e14ee9dad98000000000000000000000000000000000000000000000000001c058614b05ed4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd370000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b26d846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000cc790b22ef435b76da0bb8d7c239fbf919655761000000000000000000000000cc790b22ef435b76da0bb8d7c239fbf919655761000000000000000000000000000000000000000000000000000298197090db88000000000000000000000000000000000000000000000000001cb13f9084f640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd380000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b26e846d1e961f846d1e962d8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000001e9a07ffcc98a932aa57fa57407833ec0adc6bb00000000000000000000000001e9a07ffcc98a932aa57fa57407833ec0adc6bb0000000000000000000000000000000000000000000000000002f0bfa7b63dae000000000000000000000000000000000000000000000000001c9da97501e879000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd390000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b26f846d1e961f846d1e962d8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000876199f2d7a3463179dc26c770fccb378a1dad22000000000000000000000000876199f2d7a3463179dc26c770fccb378a1dad220000000000000000000000000000000000000000000000000002f2078c9f9f67000000000000000000000000000000000000000000000000001c16b42e7835d2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd3b0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b270846d1e961f846d1e962d8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000a4734b099118455730d1a845dbdfd0725dc6a61a000000000000000000000000a4734b099118455730d1a845dbdfd0725dc6a61a0000000000000000000000000000000000000000000000000002fef25c863b85000000000000000000000000000000000000000000000000001c5f4e94f95862000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd3c0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b271846d1e961f846d1e962d8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000a211bd69c8a1400cb429a1ff92ad292489dc7f62000000000000000000000000a211bd69c8a1400cb429a1ff92ad292489dc7f620000000000000000000000000000000000000000000000000001429fc0610640000000000000000000000000000000000000000000000000000e35fa931a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd3d0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b272846d1e961f846d1e962d8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000c2f1d21cd496bea3a8361daaee75101df4d27ea7000000000000000000000000c2f1d21cd496bea3a8361daaee75101df4d27ea70000000000000000000000000000000000000000000000000001f0b2c94f77c0000000000000000000000000000000000000000000000000002e2f6e5e148000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd3e0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b273846d1e961f846d1e962d8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000b466fd02e3e151bcbce9c97fb0a9395889114df6000000000000000000000000b466fd02e3e151bcbce9c97fb0a9395889114df60000000000000000000000000000000000000000000000000002ddeda9078707000000000000000000000000000000000000000000000000001d11453af6ce96000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd3a0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b274846d1e961f846d1e962d8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000ac2314f552fe74262d6238bd4a6af9b910bd92b7000000000000000000000000ac2314f552fe74262d6238bd4a6af9b910bd92b70000000000000000000000000000000000000000000000000002c8d66ed8a114000000000000000000000000000000000000000000000000001ae9d2ef83c6e8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd3f0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b275846d1e961f846d1e962d8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000a4fe2ec308b39ded3fa9528efb798cd13a4f267e000000000000000000000000a4fe2ec308b39ded3fa9528efb798cd13a4f267e000000000000000000000000000000000000000000000000000300330dd90090000000000000000000000000000000000000000000000000001aa0fa68dac85c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd400000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b276846d1e961f846d1e962d8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000064531e1da97c2af6108a03d9c49a2e47b5bb71f100000000000000000000000064531e1da97c2af6108a03d9c49a2e47b5bb71f1000000000000000000000000000000000000000000000000000274797fa0c06d000000000000000000000000000000000000000000000000001c4a56c7f6b6d4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd410000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b277846d1e961f846d1e962d8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000059780ded40a5c540a536644dae651c54504dcb4400000000000000000000000059780ded40a5c540a536644dae651c54504dcb4400000000000000000000000000000000000000000000000000028b423ed3116f000000000000000000000000000000000000000000000000001b943596512384000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd440000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b278846d1e961f846d1e962d8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000524a06bada4d98aa7c96275b7d7130d3180b0ff8000000000000000000000000524a06bada4d98aa7c96275b7d7130d3180b0ff80000000000000000000000000000000000000000000000000002b55a3ca2b094000000000000000000000000000000000000000000000000001ab7f60b5414c4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd430000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b279846d1e961f846d1e962d8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000a3fa534de502a31714353d91f26e1e77f6084323000000000000000000000000a3fa534de502a31714353d91f26e1e77f6084323000000000000000000000000000000000000000000000000000139d8db69f480000000000000000000000000000000000000000000000000001f438daa060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd450000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b27a846d1e961f846d1e962d8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000001d35a60b0b6815cac022f0ff5bffe4371a23ec460000000000000000000000001d35a60b0b6815cac022f0ff5bffe4371a23ec46000000000000000000000000000000000000000000000000000139d8db69f48000000000000000000000000000000000000000000000000000354a6ba7a18000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd460000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b27b846d1e961f846d1e962d8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000093f09eb91afcd7d4de05dbdfadde30d53d20580200000000000000000000000093f09eb91afcd7d4de05dbdfadde30d53d205802000000000000000000000000000000000000000000000000000241b57c404012000000000000000000000000000000000000000000000000001c4b5e4c48b40a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd470000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b27c846d1e961f846d1e962d8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000dd47d3db30809a107c1dd59417a61230458d278e000000000000000000000000dd47d3db30809a107c1dd59417a61230458d278e000000000000000000000000000000000000000000000000000139d8db69f480000000000000000000000000000000000000000000000000006a94d74f430000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd490000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b27d846d1e961f846d1e962d8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000863ca2c8116aaa8d8674a0b3491fb9fbb99ee706000000000000000000000000863ca2c8116aaa8d8674a0b3491fb9fbb99ee706000000000000000000000000000000000000000000000000000139d8db69f480000000000000000000000000000000000000000000000000000aa87bee538000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd4b0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b27e846d1e961f846d1e962d8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000012cb6f5eb0d90f8978deb060c46539e8b872550900000000000000000000000012cb6f5eb0d90f8978deb060c46539e8b87255090000000000000000000000000000000000000000000000000001398c01372ae0000000000000000000000000000000000000000000000000002aa1efb94e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd4c0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b27f846d1e961f846d1e962d8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000245722555190dc99ee569cdd1c5a67e05ce62dfa000000000000000000000000245722555190dc99ee569cdd1c5a67e05ce62dfa0000000000000000000000000000000000000000000000000002c38faa4ee925000000000000000000000000000000000000000000000000001c0e839c49bb12000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd480000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b280846d1e961f846d1e962d8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000da9a19e7831402175051adc494f67146e5827e43000000000000000000000000da9a19e7831402175051adc494f67146e5827e4300000000000000000000000000000000000000000000000000013498d5b048e0000000000000000000000000000000000000000000000000001ff973cafa8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd4a0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b281846d1e961f846d1e962d8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000099877f4429978df7552fde876e0eec345497444600000000000000000000000099877f4429978df7552fde876e0eec345497444600000000000000000000000000000000000000000000000000013498d5b048e0000000000000000000000000000000000000000000000000000aa87bee538000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd4d0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f482e70882402285012a05f200852e90edd000825208942aafe94148739a6e868e32cf8b1ca9fbc81e2d0987121e6c485ac03780c0",
+ "0x02f482e708823ed485012a05f200852e90edd000825208941981f7efb48591ee64bdaf0d61fe042e39fc12e3872386f26fc1001c80c0",
+ "0x02f482e70882402385012a05f200852e90edd000825208940d8c44854055f4becdfee63d2f6286548ace0c34871550f7dca7028780c0",
+ "0x02f9029782e70880846ba55515846ba55515830282449480e38291e06339d10aab483c65695d004dbd5c69871d21db47288000b902642cc4081e00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000010d92ea475e7ce7010000000000000000000000000000000000000000000000000000000064bc7def0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001d21db472880000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000046ec4bb184528c3aee6f1419e11b28a97f33d4830000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f000000000000000000000000a550c99253c5ee0136ac37eb610a28dfe2cc093a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9019782e7080a84623ed54584623ed54f8307a12094da4c3eb39707ad82ea7a31afd42bdf850fed8f418709a12f75a9e800b901649caf2b9700000000000000000000000000000000000000000000000000000000000000e00000000000000000000000009dd711b0cb4430f429231e5cb9940dbd1952a36f0000000000000000000000000000000000000000000000000000000001e13380000000000000000000000000000000000000000000000000000000000000012000000000000000000000000007039fb83798d979bc4697ab4e3e1bb715bf95700000000000000000000000009dd711b0cb4430f429231e5cb9940dbd1952a36f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005736869667400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000056c696e6561000000000000000000000000000000000000000000000000000000c0",
+ "0x02f901b782e7081184623ed54584623ed54f8321710694a02573c4ad15c16b48f10842aac9c9ea405b65a38701d704a97b9400b901845190563600000000000000000000000069421eeb27ca0941bf46e071098172a56b35df97000000000000000000000000000000000000000000000000000000000000006600000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000003d2a1800000000000000000000000069421eeb27ca0941bf46e071098172a56b35df9700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000001469421eeb27ca0941bf46e071098172a56b35df97000000000000000000000000000000000000000000000000000000000000000000000000000000000000002200010000000000000000000000000000000000000000000000000000000000030d40000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f86e82e7080484623ed54584623ed54f82b3cc94e5d7c2a44ffddf6b295a15c148167daaaf5cf34f80b844095ea7b3000000000000000000000000438670d41d5118003b2f42cc0466fbadd760dbf4000000000000000000000000000000000000000000000000008e1bc9bf040000c0",
+ "0x02f86e82e7083184623ed54584623ed54f82721794265b25e22bcd7f10a5bd6e6410f10537cc7567e880b844095ea7b3000000000000000000000000438670d41d5118003b2f42cc0466fbadd760dbf400000000000000000000000000000000000000000000006c6b935b8bbd400000c0",
+ "0x02f901b682e7080584623ed54584623ed54f8303b8da949e66eba102b77fc75cd87b5e60141b85573bc8e88695a0387c24e7b9018451905636000000000000000000000000f4c21df1cc8dfca3b064e9b3c9a0f434c3b9039f00000000000000000000000000000000000000000000000000000000000000b800000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000017d81d6000000000000000000000000f4c21df1cc8dfca3b064e9b3c9a0f434c3b9039f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000014f4c21df1cc8dfca3b064e9b3c9a0f434c3b9039f000000000000000000000000000000000000000000000000000000000000000000000000000000000000002200010000000000000000000000000000000000000000000000000000000000055730000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013082e7080b84623ed54584623ed54f83019a6894508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000ee99f3fb8744dd05cbbd87104b7f088d194992ae000000000000000000000000ee99f3fb8744dd05cbbd87104b7f088d194992ae00000000000000000000000000000000000000000000000000016b3f8f9ae760000000000000000000000000000000000000000000000000016345785d8a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd230000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f86e82e7084484623ed54584623ed54f82b507947d43aabc515c356145049227cee54b608342c0ad80b844095ea7b3000000000000000000000000c66149996d0263c0b42d3bc05e50db88658106ce0000000000000000000000000000000000000000000000020ddba8ea3d826848c0",
+ "0x02f8b682e7081184623ed54584623ed54f829a3e94d9d74a29307cc6fc8bf424ee4217f1a587fbc8dc881af0e2108677632cb88429723511000000000000000000000000e4edb277e41dc89ab076a1f049f4a3efa700bce8000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000200325a9ead0f563f2ccf8c7afcf1f277a42d7bd16c676881b19cc96d467244f7ac0",
+ "0x02f9027082e7080a84623ec1c084623ec1c08302d77e94272e156df8da513c69cb41cc7a99185d53f926bb80b90244ac9650d800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000104a8c9ed670000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f000000000000000000000000000000000000000000000000000000000000012c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064bc539f000000000000000000000000000000000000000000000000821ab0d4414980000000000000000000000000000000000000000000000000000011925a4182e4820000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044bac37ef70000000000000000000000000000000000000000000000000011925a4182e482000000000000000000000000be8c5b0bdfb7b19c08fbad3c085b05da15cb721c00000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013782e70880845f34f8e7845f34f8ee830390f994272e156df8da513c69cb41cc7a99185d53f926bb878e1bc9bf040000b90104a8c9ed67000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f00000000000000000000000066627f389ae46d881773b7131139b2411980e09e000000000000000000000000000000000000000000000000000000000000012c0000000000000000000000001e7bb53c1eefa4513b69fcbfa900b6bce051a77b0000000000000000000000000000000000000000000000000000000064bc539f000000000000000000000000000000000000000000000000008e1bc9bf040000000000000000000000000000000000000000000000000000000000000464229e0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9047782e70820845d5a946d845d5a947783032773941a7b46c660603ebb5fbe3ae51e80ad21df00bdd1870d801472258000b90444a71c9b7f00000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000001b5722833b955e27a35a12eeab4886b650ad810b39e0de5a25fc177912a8bf476e2a99c9092df5e2f398069970b339375fd1aba8d53eb271e4f1fc1f749d7e5eb70000000000000000000000000000000000000000000000000000000064bc4de3000000000000000000000000b1ebe120c810ab46afcbcbdad7102096fb12634300000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b1ebe120c810ab46afcbcbdad7102096fb126343000000000000000000000000000000000000000000000000000bd011e3e0d0000000000000000000000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064c57e07000000000000000000000000e49cf1bbb229562aea4045133dfd244676b3acb8000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000b62c414abf83c0107db84f8de1c88631c05a8d7b0000000000000000000000000000000000000000000000000000000000000f1000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000955af4de9ca03f84c9462457d075acabf1a8afc80000000000000000000000000000000000000000000000000001599ba503c0000000000000000000000000000000000000000000000000000000000000000041dfb16404843d231528aaba3eaa2193cfc1ce0b72893909d63a87e98524f3b08c4b61dce555815a6366ef6e5c18f6c9832f9d13cc49d81e180429ac8babb9009d1c00000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f84d82e7080b845d4cdd5e845d4cdd5e828caf94e5d7c2a44ffddf6b295a15c148167daaaf5cf34f80a42e1a7d4d0000000000000000000000000000000000000000000000000006b54a2a2c0800c0"
+ ],
+ "l2ToL1MsgHashes": [],
+ "timestamp": 1690062623,
+ "rootHash": "0x0898f77ca827e6a3f6edc35c2d8a7ee7243739441dde6ce6db590b12f660601d",
+ "fromAddresses": "0x46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81be4edb277e41dc89ab076a1f049f4a3efa700bce880c67432656d59144ceff962e8faf8926599bcf8e4edb277e41dc89ab076a1f049f4a3efa700bce8a550c99253c5ee0136ac37eb610a28dfe2cc093a9dd711b0cb4430f429231e5cb9940dbd1952a36f69421eeb27ca0941bf46e071098172a56b35df972d584a6d566ea2cee64ea18dddbcddacbb7f681f8a0df28f173103e20de05093aa6e27829715c9dbf4c21df1cc8dfca3b064e9b3c9a0f434c3b9039fee99f3fb8744dd05cbbd87104b7f088d194992ae939a7ef73572e9218c7085aca6d894bc237f15e3aba9e8b0150c6e612d9d49ff8d0ddf9aa4160ee9be8c5b0bdfb7b19c08fbad3c085b05da15cb721c1e7bb53c1eefa4513b69fcbfa900b6bce051a77b08a9b4221a84bb39faa7d6fe0f7664efec9511acc5250939ab0e1aaf6680d54a2b2154e59a43871e",
+ "batchReceptionIndices": []
+ }
+ ],
+ "parentStateRootHash": "0x0db5a89c27cebc50b75b532636687ca2662de28a5341c5b3d4bcb2b4f4e0b9e4",
+ "proverVersion": "local",
+ "firstBlockNumber": 33118,
+ "DebugData": {
+ "blocks": [
+ {
+ "txHashes": [
+ "0x33b8ad2d347b76c3c78e5ecfe521c283ccccccd72c5195880dae9d00f5eab980",
+ "0x15d34713dbd2b309937e1f53e88c77ad50c6f68c96ecac2116b8f8b854381239",
+ "0x31347092ee78bd11582f48825b094e668933677bc1cc606cc317a0223741f699",
+ "0x9c68df3eb4bf8ed1d92d84ae53567574d591c8ddce51d8b34aaba6b2ccd16888",
+ "0xd82b87e4c4ee4d3b108c44d05b7b46513515a66a805b12d2f413f1631e5d8ce1",
+ "0xce4ec8617cdd1fba8463bb76f3767e155af6cc63db1557a697c20065693de49a",
+ "0x06e6ef03f7e67dcc60ae4160966bec0b5a058c92e918b44c62888ae0fc2d27d3",
+ "0x34611763b2a2f865b5df2264cdc3c049aae13e87ef13d77e21f4316fdd2c5c2c",
+ "0x7eed17b099e00c62c4d432f273ac2d22339040f94dcb9241968819ed8eec33d5",
+ "0xb7163f67cd7d2172418f5deb86a62c067f3b7bccc36d3ebc636d7982847f19ce",
+ "0xce223cd4c2cf0acc26af1041bcdbac3823fca09e09db5cd43581ea14c52ada2d",
+ "0x44d4ecff38101357ee1b74d088d0a9e3ce2852b737a108c848e121dd17973dc8",
+ "0xd83b699282e9e087e95f8554101c16da3ed1135d89a75374232b31867745a41c",
+ "0x5eab0f03880003241b85e2d68f3d2ba8dfb63cf67bff90b6a0c3e2fc30ba34b7",
+ "0xfa0abaf3bb7ce0afd7b0fd21ab78eb79372dfce8faa1d68f8f8dfd6ec83f8c66",
+ "0xafc599712a9a10f6311baabc78c270946698ee80e2598b8ad48e2fd6ae8aeb49",
+ "0xd310932c47ff9048b28e2d5082ce413504bf53d23425a15f278822b00720c2a8",
+ "0x5eab0f03880003241b85e2d68f3d2ba8dfb63cf67bff90b6a0c3e2fc30ba34b7",
+ "0x020f58d8a04fb1b64567234d18ee4e33e847b2ad6af1d486fc8c912afa82764b",
+ "0xe9135db60ba9cb6b1b3f81de31e8530c63757065d9e562033a90e43182c9c0d4",
+ "0xf9a34e4dd66ae14a2dd4c03c52cef0933e865f5dd3b5b311069afebb4914fc11"
+ ],
+ "hashOfTxHashes": "0x34a0bf05552d21800f3a4346c9fe2071767daa22f0fac5ecba21f0e356ec87aa",
+ "hashOfLogHashes": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
+ "hashOfPositions": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
+ "hashOfFromAddresses": "0x0387605e3468cc9ceecd4d5a9c7282899879523dd6d98418fd6a8aa787fdd4cc",
+ "HashForBlock": "0xbb50f9fb374ec77f1060aeea1fbddf182feee9c2c17664e3ae5dd9b13fdfb38d"
+ },
+ {
+ "txHashes": [
+ "0xe1e39f8c4731df31d1e9acc2178dcf7c410d839a708d42b049dc09870c144b8b",
+ "0xd22c7b46589c2c84168199b2522322983d6841dddb95a2db7b73eebb146d0989",
+ "0x251ff80c257ebc858547507fb7393efdb53be26bf6ff5bff60f301bedf5b94d2",
+ "0xabbd49c232d04197d5ec243326a11e5960da70d8fa7bdacb96b80c79035e59c6",
+ "0xb37b55c44982c87b90fc8c6894598697838f300f254a521efe453141e5c73ada",
+ "0xf8e6cb20fe34af5a9139c815493b1cf368ddb6f08cbe7ac87446e59beb45be2f",
+ "0xa252d9a0d4d5cab3f9fac8c3b2a01493aa217e20485b03a7c4e8434752210f08",
+ "0x2a0dfee0cd5a49395ed751442e1a48df0a98b144da058f631cca548860980da7",
+ "0xb27047284f817bfba3aa35c83a566b285f918b08da193cba0f57230ae653124f",
+ "0xb01d7705691db2c1fa48e2790704542a50b6fce60469626ae2ed21193fc5d9a5",
+ "0x6515e946304e6907a1c85b02938f9f5f896a7966b2479350ba3db075293fcf2c",
+ "0x1860df19170373188821b9e11815d7efb3150d291e6171d0c37fbaf5604e0ed7",
+ "0x2f168be2953e5b030453a8dc00624425a5f5e05fd8036182b2fa584136e47f18",
+ "0x781c200c88b39cf217b8484955e220b950fdd1b49752324ed6ab2952d5085d01",
+ "0x588ce20ef4021ff9242e1e9ff98457a668e3f52fd32a4f96df64cf7352b4e6ad",
+ "0xb85a6b9fce54bd60b07aa31c2edb5aab2e835d34b53f649ec4a650b056981eee",
+ "0x6d10ddd6d9d92e06c1254d7262ba5e34c817c0cacf7d36238502e0d3a918b33b",
+ "0x0ea5515f39e5fd39dc5bf490794f5e23270f7b020727972f3d24ec5e2e3c8e03",
+ "0x9a9fd46d45e2acd5b7f0c19194ddb1262a4de9474b0f662496339a99ad867e32",
+ "0xde8039adb7e0be4f80438e1b3c0a8a6ded538b602e90998083b98d8a04d434f2",
+ "0x1c459d9b02928bbef00cfdc579d4f69cc4152b0f12a49f92873ee579c5b3cca5",
+ "0x3e57e9ac19b84df49d9bcf1c90ac40f4607e63445f5d3e728d76d9b6e749e844",
+ "0x74632667d6f68741930f8f9c40f8a0762865fb9388e03ca70377aff3225e7462",
+ "0x033505da513d29c7f3f8a49ad7ca1e6215be9bf6ec0db3b1e85bce79a41634c7",
+ "0xa3f969c864f09c6d66a4208aa5773ebc018ee175153ec0e95a7d195214f443ec",
+ "0x47a10f787f700cde6dbb742f82b38b60595522a5b37a3e71142a464214d12005",
+ "0xb288d2887509b067194d38e298401648b70f7c6e073aec5035d946f0cc96eee6",
+ "0x55ce7f07dfc65be1997f829cc48c17fd648dda59a20f15b0838973c4acc2b6ec",
+ "0x9638728c2348797024b771a7f11a7f9ef24ac0ef47e0f6c07748b5d4087beca1",
+ "0x96695bab277b9a8df0e26c73dadaa2489f7f32d2faff836c39fd1cada7f53e45",
+ "0x30c3fa2924cea3a4b6971e54e7a9e063d03bfc007c381e98653c6058673fbe64",
+ "0x296b77172fe6ca7c93f3a27cad3469fe328f38a6b7d62bff8eb113c562811648",
+ "0xf4fb520848b590bfe47bcf93e9cbf61f77a3d9a2a46ce63711aef77e1252e845",
+ "0xe0c716e1abe1ced5af4a5d16068bfeefe71b6b4b0dafeb52dca0e4d62437c6c2",
+ "0x5504974a707746524142e4d87a85158b6e0c1669f8f7ca8c79a7b0c4ab736e5c",
+ "0xcceafae6719af922d536578ed8d9d80b6f9a034dc6d7220543edcc3aeb50b112",
+ "0xab5ffedfa4d3aebc9fd5b5711fc1333df8bda6e6c83d8795479006ca669913b9",
+ "0xf3d3fcdaf453a7ce863571db913b9e49e43f23f8bc21f9edc0e7ab08607c5b27",
+ "0x697fae72571c7445c528179adfb08739b50654713bcc21093ad0ed0bc2343415",
+ "0x6e26e4d0ae4d713d0d68b7da5cd53ee352f3aa020887014225a0991a2a047259",
+ "0x4a9b986c3b05b410aa1f304c914b0c89eecc71a8e23c0d235a81b3c57e76112c",
+ "0xd8b85bd548e76cbfb53679485bf4af4bdadea983abdb7c5e3958e945144f53ff",
+ "0xcb4d071107666f474f15da1571a8cba5a15d15b8fd6b4a1b7f1b0ba2f8e2a3f6",
+ "0xdd3d6ea98fdd96698f5f13c59e5cdb51f97d02991735ff7b5bc880af4834cb00",
+ "0x2259ed1a2d2ed8462338da523a4dbd29c38d1662de859ca583337663700834e9",
+ "0x6bfcea9251674921d5bf38e17f4ffd92a0a89d1b54910bee0e9e89ff53f49efd"
+ ],
+ "hashOfTxHashes": "0x23d15228532fbb55efd61aca03c88cf041fe29a5fa7bcf2bffec9384f0e1bfed",
+ "hashOfLogHashes": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
+ "hashOfPositions": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
+ "hashOfFromAddresses": "0xf1c8491fc9e3379cca6f1b00229d29f288eb5cf50bf41e2de97f7dbba03560b1",
+ "HashForBlock": "0x62ccbbbb86ef488aeeefa412012b550d385ac709ca4edd5549dcdd1d02c49e70"
+ },
+ {
+ "txHashes": [
+ "0x79c5454b996125abe3a3f1b2308e12bc1a05fbb18ccbd961d1e9ed5a60e99a3c",
+ "0xb48f595c9d8b0b3b430a3e18ce714b338d24e151444c802af42c160e83e1c023",
+ "0x1d33b875399ea2b977069a7a3af5af502816bfa57370cfde72b40b797e2cdf9e",
+ "0xf91e12a540222c88104dd71c2cb312651d5c6dffaf4a397b9b823ce006dc35f6",
+ "0xe22e54f211a9dc27fa8e306b760b41ee2430c7c5a6844b034275cd64d7f682cd",
+ "0xdecf2c8e07903f8fac04580c68b202b99b24eb56d6c9c34204718055169f35e1",
+ "0xc65db255285d080713ff417da3054e95df469126b79c0b4b1dd394ac17441683",
+ "0x066f6cbd57a6747706d1611bc48116ff088aa12174d9416a6ff8ff2c4afe33ab",
+ "0xbfc927e159210db0b7ee968257c58b94f55a203b9ff8f9cebf79e38d2b50de86",
+ "0xad05ed5e0b6f984e3ba8a022d0bbcd0787c0a4cda53d18d0de158c4bfe43972b",
+ "0xcfd19e2cfeebd293c1e087e61be6f9b632fbeb72e01b75af7827dbe8b041b9e8",
+ "0x56f4dfd08b403881832831981bbe7fcfbcd0e8da08994793457617793e63d597",
+ "0x8e67aea74c01a3ea7d16a6b9b47efb220e6816ba9e6029944925fba4be554306",
+ "0xc1809842c56bb4d49ef49c35c5c2016a3979f2f467f3eece542f814a1b308474",
+ "0x4309dbc805acd30e711c932cdffed396b097f18edb2a27310851e275ced82711",
+ "0xc2518d2b1ab1d703158e013adf992efc9696f5bc3b807cf3abed054a30529652",
+ "0x0c1bb65bdc1c24fa132c37504059fce12e3edfd3b9c5053797756ee25f07628d",
+ "0x0ecea1436ce18b7c66c6d9e0690506fb974393608267ddc64759d80365082843",
+ "0xf3d59eaeaf5f227e11c4e676b7789c0ad33940716e091bc5278075bd69019327",
+ "0x88e39cd7ba151a9f8f8dc5c3eaae99377f444b34d9be70404b19149efa6dfee9",
+ "0xa6768e6b696584e34764d4d853b5506ca3e21f6030ad576b5d40264382a7c634",
+ "0x604a18caa65f2241536c5f28cb8c5d2e1f2d8c686f9788137c4f24576a10cb52",
+ "0x2f5fea3ed9bbb33919946e2ea03719dcc12a1ff7267a84a2ec432f08b8a1723d",
+ "0xd209e9dd0197a7c10cffbb502b7f4af0714a4b50686d306a270f61e4855f6ebf",
+ "0x158f1510fe03b404484a2b8150ab0730ef8825854cc80977d4f8a3ea3fc7b759",
+ "0xef7487da45ff07d1ac218c7f192305b556b0d5b0906e4d940c5c9dadd50e3c8c",
+ "0x372ed95455b0932ce3bf40d6a8a35de722c626c513f3d92f4b774e8c89e119be",
+ "0x04b8ca4b2734f82a5327a905be3828d8689772d4104222b3f7f15eac2e41a868",
+ "0x2c15d3cc010c7cf663e30f34034743082d5dd002e5978c1f4781c4ff74cad8bd",
+ "0xde23d5166a4751afd2f80792929b9a699d9583d1c3ab84f327eeb09fbb720779",
+ "0xaa58f51031042cb889097716b22be59129f72504d5b9d6633e97a30ed5598c51",
+ "0xc74f177217d41c5b70ec6a523f277cf91a331a0450aac5b7dbc4ae18332c4652",
+ "0x67c43525a6d0d8b45c9c85f1ff260059338a0ad6ad64d456ca9dbfdecddff06f",
+ "0x49d8163a4f3f04ef21cf35fe7a82384ad926fcea2dcfe2a9f01825cc0fa1be2e",
+ "0xe8986d3c28f95a68a57ab7612a6dc801ce17c615cc9ca3dfce9ab6ab28e647ee",
+ "0x6e24f6034ffe2a99719606173f177f119a848f959976fba77d19c5d20523b196",
+ "0x3dd9defe080c662bece7e824f5af8cc6cebcb5d30c1023ea699f2021d2997c10",
+ "0x2f4b77b8a052f4b2fe9233104d6f51bb0c312f524bf315eac8c1544448f13340",
+ "0x7851ce66a67cef5c273839c5c4eaf2a4a1ff58e86c7b9342aa9743e4c462b079",
+ "0x6839a1b38d40df547c006156e5e82f03be4f3e3bfd9fa744cee1c3ec6c6c0b15",
+ "0x271c0dab45c8118a83fb964403263ca7fc4ab44ac86155732b6f66fa08233e0e",
+ "0xcfe46e3f690749455af944d0e9dd3195056898852da82c1298ba63bdf907a37e",
+ "0xb771169740aa9d7d2ed14d10d6b511c960135c877a0eb47cb41d557828268875",
+ "0xc120090ac715d0c88ed0652ca3c81727ebeed0a98b546c71db314decadd0a26d",
+ "0xd452e7c1936098aa268625f147892d1fb3e8e06ba4b9ab2b91a9950644f966c6",
+ "0x27e29d73fa87e9a21a2c2270031edadfeceb2f77392e2dc86023f814775a872c",
+ "0xd499a99d846b7012f01b53a47b8fd1701d89585c59179ea57a19f1de36b81e20",
+ "0x695bea22f78cc9fb72a191911ca33bf1183c7f98e9e34cb2a7e905cb75407c0f",
+ "0x265fabb3192edf158edf7074d30645e4dd1875c77c5d086089feb20622f5b88b",
+ "0x96e46b4eff69253f37399723307a09b3c9de0979db5c48d63e80bc418a57b5bd",
+ "0x48e0a7cfbf088dd37b003dab637c7431ac817220a4d0f9228d6371571e8281ca"
+ ],
+ "hashOfTxHashes": "0xd6407d1b3333a2efc5d37194c55eea9cb8e8fad53fbfd7d009ea3d5019c75128",
+ "hashOfLogHashes": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
+ "hashOfPositions": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
+ "hashOfFromAddresses": "0xf9f76121ce60580d7997cec266645cb12da15caf882ff0075829c2b7243ece91",
+ "HashForBlock": "0xf0e25e3c06a11aa02737a878098dbd5b246c46f29b4f45244c14024e8b22931f"
+ }
+ ],
+ "hashForAllBlocks": "0x0b7ef7c824a8ea7f49231802e8bfcb992f756fdd48e086c8f992f63caf1cedca",
+ "hashOfRootHashes": "0x7dcc64e8c3e428328d3f95778f7340d5fe39015147dc430bdbfb0901892edb6f",
+ "timestampHashes": "0x4662757f920ee71ddf31fa1a88c04f98fb63ef67a3032bc34c12a3ddacba363e",
+ "finalHash": "0x3041a1027bacc392df45fe2e0a35293974440621ecf1efca11e5359819869829"
+ }
+}
\ No newline at end of file
diff --git a/contracts/test/testData/Full/rollup-2.json b/contracts/test/testData/Full/rollup-2.json
new file mode 100644
index 000000000..de4351186
--- /dev/null
+++ b/contracts/test/testData/Full/rollup-2.json
@@ -0,0 +1,118 @@
+{
+ "proof": "0x25298f02ccff1c6248a7704989b66740ddae2e76f3f1bf554f5b2cb35973556b21601cb0926efc5ec948bc190e1ab62233c18eaa1b8c38084622111ed9b032ac30304cb2ab7ebf955b74e9232c149739e373ea585409b8cae686115cc7591add2caf57079589d28cae603a34013eb8a8d9827dd5e4c9d4c202cb435561ec2026112225c2ab87ed8367060f74ae635282f10304985ec966165a0f7db6de1eef2c259a82d15471c60c25f7e15431beb15a748295a64f62c757d6e76f4a3bf5e5ee18ff669ebfa05c93d3743bee490d50a388703a332aea81ff15721f4d13ca90752b6b444eafaa7a1b12a2eb2baed683fd28bf308101eaca7d14041047eb3877291764b3af20c4473ec0d4f7221e7f7c7c95344fe4c0156aa7234c479255d0428d1cffb47ee3adf8dbe89638ebbe7ce8fac5503366c388d2e374ad38bb34e6585f0da77e8193aa12c6b5630273bcda2943dec13417bdb0b63497f5864c06b41cfe05b1d0103997a4b1dcb5dac6fdb6d029b1ae2e72216b2b96565869d33b1d95791a084fa2a716ff71fb2fa3a34e035129a95ceaca4e502a2eafb284d0d10187ba2f3297338f680b620baae8df8243f71ac63d60f057b9a78dba5b747da13fa626261981aca3617d141acb54c1e9de7b25539ac429f76314daadbf13e1f8b43e8e300414bcf57e1965bae082cea74d4314cb1eb1c60631940280142216cc9c0b17235ab7fcb2325fafe70afe65e7de329c4326dc9f529bd87bae866e0ec575bba304945d73b7d7eebd2a09f5ebecd31a9b9fd70bc68192a7386c59cc0c18d28992152371798bc7929d766aa25bea1b586b738f014818add47a28f923c66dafadb32d6a48fd5b4ee8f1fcfd556bfb1f9cae6b22145bee37b0883d16deddc9d419771b8bb6dedf2a7afed8e2fe9369af5c385a55c1c39422d1ead817d31d7d4f1eef2b64cd5844fad8e0b7b8f95493d1eadfa880b8fba7253f37dc78a92b6f29c0fb2cd27d475058d124d718624fbbecc492aadcbe6c752dea9f4393456726fb162a26d589a0dc3e7d3ed055582f1d90e7d6b36289d3ac3ac32f672fde81b68778583003185d1dacbf851974d60fe615fa6d77c8f6f161faea39ff542bfecfb126951af2ac86bd90fa127b8bc273664b923080aa4a3642367a0c05d9f7cc2b01cb320e418bdadc657adb947a88829e2384c4614e7aee9716999aa8c716eb9798633e028b91a3689ba11987bb364fe9a3d0a63dae987eca63583a060559371c05cc042a4a5500602de915032521b45046afdd99034383cc6202c1f802540445237b51",
+ "proverMode": "full",
+ "verifierIndex": 1,
+ "blocksData": [
+ {
+ "rlpEncodedTransactions": [
+ "0x02f482e708823ed585012a05f200852e90edd0008252089460fe7c6f554cc499eaf468d436f4985f31f57430873281606e20801680c0",
+ "0xf08084713fb30083031eee94a02573c4ad15c16b48f10842aac9c9ea405b65a386d12f0c4c6000841249c58b82e7088080",
+ "0xf8500184713fb3008302c8ba94eba2e9bd066dbce1b93a4007ef5431c234cbc7ae86763bfbd22000a41e83409a000000000000000000000000e0560dadfb44d789054f09f3a24dfc1771fb39b582e7088080",
+ "0x02f901f782e70806846ba55515846ba55515830497c59480e38291e06339d10aab483c65695d004dbd5c698727048bc1f6f5bab901c494ec6d780000000000000000000000007f72e0d8e9abf9133a92322b8b50bd8e0f9dcfcb00000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000006868dbe5fcf4261000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000007d43aabc515c356145049227cee54b608342c0ad0000000000000000000000000000000000000000000000011af3865a8f45923200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000027048bc1f6f5ba000000000000000000000000000000000000000000000000000000000000002000000000000000000000000010340719c67d9f66d2b49d6c510946e4723e93c80000000000000000000000000000000000000000000000000000000000000000c0",
+ "0xf901b202846b49d2008307395494db3bb6d5a8eeeafc64c66c176900e6b82b23dd5f86930c5d2b0686b9018451905636000000000000000000000000c1a8da2603bf579df012309768c021ebe8383d3300000000000000000000000000000000000000000000000000000000000000b800000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000001ab55f0000000000000000000000000c1a8da2603bf579df012309768c021ebe8383d33000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000014c1a8da2603bf579df012309768c021ebe8383d33000000000000000000000000000000000000000000000000000000000000000000000000000000000000002200010000000000000000000000000000000000000000000000000000000000061a8000000000000000000000000000000000000000000000000000000000000082e7088080",
+ "0x02f482e7080b84623ed54584623ed54f82fe0e94e5d7c2a44ffddf6b295a15c148167daaaf5cf34f87071afd498d000084d0e30db0c0",
+ "0x02f9013082e7081684623ed54584623ed54f830408dd94c66149996d0263c0b42d3bc05e50db88658106ce80b9010418cbafe5000000000000000000000000000000000000000000000000491166ff023ca5e100000000000000000000000000000000000000000000000000035502ab09541200000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000a0dce2188ef644caa8f93954f3989eb771b99e9b0000000000000000000000000000000000000000000000000000000064bc53c500000000000000000000000000000000000000000000000000000000000000020000000000000000000000009201f3b9dfab7c13cd659ac5695d12d605b5f1e6000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34fc0",
+ "0x02f8b682e7080384623ed54584623ed54f83017c2b94508ca82df566dcd1b0de8296e70a96332cd644ec873ecb9f85e3a747b8849f3ce55a000000000000000000000000940346ef1c275a5f5449a90830616fabce0a6bd400000000000000000000000000000000000000000000000000038d7ea4c6800000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f87682e7080284623ed54584623ed54f8303ee9d943c5b31b158dcaba76df46bd853c8af3ccf0f00228701e0e0dc5175f3b8441e128296000000000000000000000000000000000000000000000000000000000000009e00000000000000000000000000000000000000000000000000000000005acf4ec0",
+ "0x02f86e82e7080584623ed54584623ed54f82b4b794c5ff010aefbac255f5e2251660794feb4638191e80b844095ea7b30000000000000000000000003a5e791405526efadf1432bac8d114b77da3628c000000000000000000000000000000000000000000000000012cf746b3996619c0",
+ "0x02f86e82e7080284623ed54584623ed54f82b60f947d43aabc515c356145049227cee54b608342c0ad80b844095ea7b300000000000000000000000080e38291e06339d10aab483c65695d004dbd5c69ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0",
+ "0x02f86e82e7080984623ed54584623ed54f82b4ef947d43aabc515c356145049227cee54b608342c0ad80b844095ea7b3000000000000000000000000438670d41d5118003b2f42cc0466fbadd760dbf40000000000000000000000000000000000000000000000008c3bba747f062960c0",
+ "0x02f86e82e7080884623ed54584623ed54f82b4ef947d43aabc515c356145049227cee54b608342c0ad80b844095ea7b3000000000000000000000000272e156df8da513c69cb41cc7a99185d53f926bb00000000000000000000000000000000000000000000000d108bff2095240000c0"
+ ],
+ "l2ToL1MsgHashes": [],
+ "timestamp": 1690062635,
+ "rootHash": "0x2ffa2a815b21eca63bd8e30af2b3ad1fed2ed1a9d9538907a79c0603df3ea92d",
+ "fromAddresses": "0x80c67432656d59144ceff962e8faf8926599bcf8e0560dadfb44d789054f09f3a24dfc1771fb39b5e0560dadfb44d789054f09f3a24dfc1771fb39b510340719c67d9f66d2b49d6c510946e4723e93c8c1a8da2603bf579df012309768c021ebe8383d335f37e908b47eb126a03bf4acdf1f31450d2d0243a0dce2188ef644caa8f93954f3989eb771b99e9b940346ef1c275a5f5449a90830616fabce0a6bd420bae152846374d7572c1fcfe182e6bb5cfcb13124145a26832ad2c5aa985b1debf442e4a70554c1de705471bb4414bfdabe09f52665914ed0e572542054365058f0f0dfe53a3adb36d34a1849d0430a4f54744bfb176cd6f84778801da5a07611e941e6",
+ "batchReceptionIndices": []
+ },
+ {
+ "rlpEncodedTransactions": [
+ "0x02f9013282e70882b282846a476890846a47689e83017d9b94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000007f41f598c9ee3a2e7e91158ec3887dfbbd2330000000000000000000000000007f41f598c9ee3a2e7e91158ec3887dfbbd2330000000000000000000000000000000000000000000000000000097c147684ca0000000000000000000000000000000000000000000000000001f9e80ba804000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bcf50000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f8ef82e7082b8477359400847735940c83033fc094c66149996d0263c0b42d3bc05e50db88658106ce80b8c402751cec0000000000000000000000009dd6ea6f9d1fba5ed640651f06802e32ff45522100000000000000000000000000000000000000000000000000f06f81b2624271000000000000000000000000000000000000000000000000534e176575e010c600000000000000000000000000000000000000000000000000029a870e842d0900000000000000000000000009dfeb03893c810bac4713ee09ebde83138a7a320000000000000000000000000000000000000000000000000000000064bc53d5c0",
+ "0x02f9029082e70814846ba55515846ba555158302641a9480e38291e06339d10aab483c65695d004dbd5c6980b902642cc4081e000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000141952cbbb0c010000000000000000000000000000000000000000000000000000000064bc7e090000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000600000000000000000000000007d43aabc515c356145049227cee54b608342c0ad0000000000000000000000000000000000000000000000009275143d32c82d5d000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000007f72e0d8e9abf9133a92322b8b50bd8e0f9dcfcb00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000600000000000000000000000007d43aabc515c356145049227cee54b608342c0ad0000000000000000000000006810dba12317406828d64b07d34ad22338644bb300000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9029082e70803846ba55515846ba55515830250859480e38291e06339d10aab483c65695d004dbd5c6980b902642cc4081e000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000aa8e6a305a5f150000000000000000000000000000000000000000000000000000000064bc7e0d0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000600000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000000000000000000000000004db17dc48de15f5cc000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000007f72e0d8e9abf9133a92322b8b50bd8e0f9dcfcb00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000600000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000de705471bb4414bfdabe09f52665914ed0e5725400000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f86e82e70801846988fe1b846988fe1b82b4ef947d43aabc515c356145049227cee54b608342c0ad80b844095ea7b300000000000000000000000080e38291e06339d10aab483c65695d004dbd5c69000000000000000000000000000000000000000000000000201cbc28081cd660c0",
+ "0x02f9013882e7080384623ed54584623ed54f8302802e94272e156df8da513c69cb41cc7a99185d53f926bb8804855d8cb852ce13b90104a8c9ed67000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f0000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000000000000000000000000000000000000000012c000000000000000000000000b73bab39b5f7b709ddbb06c2eca6551f9303a7620000000000000000000000000000000000000000000000000000000064bc53b700000000000000000000000000000000000000000000000004855d8cb852ce13000000000000000000000000000000000000000000000020f141c50c9bda86d20000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9027082e7080c84623ed54584623ed54f8303a82894272e156df8da513c69cb41cc7a99185d53f926bb80b90244ac9650d800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000104a8c9ed6700000000000000000000000066627f389ae46d881773b7131139b2411980e09e000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f000000000000000000000000000000000000000000000000000000000000012c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064bc53cf000000000000000000000000000000000000000000000000000000001ee9885300000000000000000000000000000000000000000000000003d80b3b95e3f9bf0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044bac37ef700000000000000000000000000000000000000000000000003d80b3b95e3f9bf0000000000000000000000004de89c2ae04a31cc2dba3e2da11a38ddc43682b900000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9029082e7083984623ed54584623ed54f830264329480e38291e06339d10aab483c65695d004dbd5c6980b902642cc4081e000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000001ac9ae3b2c767970000000000000000000000000000000000000000000000000000000064bc7e040000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000600000000000000000000000007d43aabc515c356145049227cee54b608342c0ad00000000000000000000000000000000000000000000000c31f1cf83c3d8fb3e000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000007f72e0d8e9abf9133a92322b8b50bd8e0f9dcfcb00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000600000000000000000000000007d43aabc515c356145049227cee54b608342c0ad00000000000000000000000012e7301eef23a02c5162dce59c0b8ba264dc92e400000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f482e7084384623ed54584623ed54f82fe0e94e5d7c2a44ffddf6b295a15c148167daaaf5cf34f87024071757c8c0084d0e30db0c0",
+ "0x02f88f82e7080184623ed54584623ed54f830222a094009a0b7c38b542208936f1179151cd08e294383380b864c299823800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000c7d8489dae3d2ebef075b1db2257e2c231c9d231c0",
+ "0x02f86f82e7080a84623ed54584623ed54f8301033094e5d7c2a44ffddf6b295a15c148167daaaf5cf34f80b844095ea7b3000000000000000000000000438670d41d5118003b2f42cc0466fbadd760dbf40000000000000000000000000000000000000000000000000086dc6b3bac8000c0",
+ "0x02f8f582e7080f84623ed54584623ed54f8302ab9a94c66149996d0263c0b42d3bc05e50db88658106ce86375928718153b8c4f305d7190000000000000000000000007d43aabc515c356145049227cee54b608342c0ad0000000000000000000000000000000000000000000000000195a2c30de3e2b3000000000000000000000000000000000000000000000000018d85e81cf8ed860000000000000000000000000000000000000000000000000000363dc65ac14c0000000000000000000000005029056a3581629953feba7cba2446b6057295160000000000000000000000000000000000000000000000000000000064bc53cfc0",
+ "0x02f8f582e7080f84623ed54584623ed54f8302bd9b94c66149996d0263c0b42d3bc05e50db88658106ce8629a862c316b6b8c4f305d7190000000000000000000000007d43aabc515c356145049227cee54b608342c0ad00000000000000000000000000000000000000000000000001314d071f8ff723000000000000000000000000000000000000000000000000012b31e3240d1608000000000000000000000000000000000000000000000000000028d3191b58d100000000000000000000000078ea6f35af55843f9d1d654f4a076ef435fa34690000000000000000000000000000000000000000000000000000000064bc53cfc0",
+ "0x02f8f582e7081084623ed54584623ed54f8302ab9a94c66149996d0263c0b42d3bc05e50db88658106ce8636c606f514f1b8c4f305d7190000000000000000000000007d43aabc515c356145049227cee54b608342c0ad00000000000000000000000000000000000000000000000001916c78ac7cf0c50000000000000000000000000000000000000000000000000189652e948eebf4000000000000000000000000000000000000000000000000000035ad962d9ec3000000000000000000000000cc6fc47b6a3a03b41f43fd33c699a1ac32e3b6910000000000000000000000000000000000000000000000000000000064bc53cfc0",
+ "0x02f8f582e7080f84623ed54584623ed54f8302ab8d94c66149996d0263c0b42d3bc05e50db88658106ce863f61aef7e2a8b8c4f305d7190000000000000000000000007d43aabc515c356145049227cee54b608342c0ad00000000000000000000000000000000000000000000000001d0829a2d78854200000000000000000000000000000000000000000000000001c7384f6a0059a100000000000000000000000000000000000000000000000000003e1d2b780c34000000000000000000000000d7c0fb08b29ea8bbca31b51d6d7bebadf288e2af0000000000000000000000000000000000000000000000000000000064bc53cfc0",
+ "0x02f8f582e7080f84623ed54584623ed54f8302ab9a94c66149996d0263c0b42d3bc05e50db88658106ce863d8d1586c10fb8c4f305d7190000000000000000000000007d43aabc515c356145049227cee54b608342c0ad00000000000000000000000000000000000000000000000001c31856249176a000000000000000000000000000000000000000000000000001ba12bad1eab6d000000000000000000000000000000000000000000000000000003c51f1417fc1000000000000000000000000326b931b50a190704341c4f832ebd7a75f4e86290000000000000000000000000000000000000000000000000000000064bc53cfc0",
+ "0x02f901b682e7080b846226c689846226c69383221e8f94a02573c4ad15c16b48f10842aac9c9ea405b65a386a1c0ee12c400b90184519056360000000000000000000000006abc316192b43dc64c79ec841ac9546f793022c400000000000000000000000000000000000000000000000000000000000000b800000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000003d2a1a0000000000000000000000006abc316192b43dc64c79ec841ac9546f793022c40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000146abc316192b43dc64c79ec841ac9546f793022c4000000000000000000000000000000000000000000000000000000000000000000000000000000000000002200010000000000000000000000000000000000000000000000000000000000061a80000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f8f582e7080e846226c689846226c6938302bd9b94c66149996d0263c0b42d3bc05e50db88658106ce86331b87492b38b8c4f305d7190000000000000000000000007d43aabc515c356145049227cee54b608342c0ad00000000000000000000000000000000000000000000000001768e69ac701199000000000000000000000000000000000000000000000000016f10af3d78113e00000000000000000000000000000000000000000000000000003215db9ebed50000000000000000000000002183ba8662f9f0e1bebffab11e31ba8cae53bcec0000000000000000000000000000000000000000000000000000000064bc53cfc0",
+ "0x02f84d82e70808846226c689846226c693828caf94e5d7c2a44ffddf6b295a15c148167daaaf5cf34f80a42e1a7d4d00000000000000000000000000000000000000000000000000c3663566a58000c0"
+ ],
+ "l2ToL1MsgHashes": [],
+ "timestamp": 1690062647,
+ "rootHash": "0x10eaf4f5d5411b3faa7ebfd4d08eafd71c0aa2967f2b65ffe2eea4083b51ef2f",
+ "fromAddresses": "0x46ea7a855da88fbc09cc59de93468e6bfbf0d81b09dfeb03893c810bac4713ee09ebde83138a7a326810dba12317406828d64b07d34ad22338644bb3de705471bb4414bfdabe09f52665914ed0e57254c624f1931ec68f1b0cf01a844db352f2be2bb293b73bab39b5f7b709ddbb06c2eca6551f9303a7624de89c2ae04a31cc2dba3e2da11a38ddc43682b912e7301eef23a02c5162dce59c0b8ba264dc92e4a6dff484a46f90e5d03ff91e766ac70cf1e0eecb944f99131d1ac989dccbe3fc8fb384438b9ddde652e085691b4ae78da51187a5e9d8381f08fe2bd35029056a3581629953feba7cba2446b60572951678ea6f35af55843f9d1d654f4a076ef435fa3469cc6fc47b6a3a03b41f43fd33c699a1ac32e3b691d7c0fb08b29ea8bbca31b51d6d7bebadf288e2af326b931b50a190704341c4f832ebd7a75f4e86296abc316192b43dc64c79ec841ac9546f793022c42183ba8662f9f0e1bebffab11e31ba8cae53bcec7dc130e21cf30c5e3adfc46664f55ff2c49502dd",
+ "batchReceptionIndices": []
+ }
+ ],
+ "parentStateRootHash": "0x0898f77ca827e6a3f6edc35c2d8a7ee7243739441dde6ce6db590b12f660601d",
+ "proverVersion": "local",
+ "firstBlockNumber": 33121,
+ "DebugData": {
+ "blocks": [
+ {
+ "txHashes": [
+ "0x3efac55a8dabbe5b488cf40a67b0d9b4400091b65d778e69753a9febf28ba3ad",
+ "0x2bb65c44a88817c33d827680ad7f019a786eaa2cfb2728f95d21fa6f9763a4c7",
+ "0x237b7d6fd52e10a9a324d66eac814a5fdc4ca9021f50b1b2c517bc0c1164e1a6",
+ "0x13e724a8efc49fee29bdba2d7d737aaef311703ca6bebea97c9b64361c201717",
+ "0xf56bda292071c06ac1fbbad2679ad9b5ed4e724aa41489510f7db59675799b71",
+ "0x24d9e924cf2a5f5400664f51d9dd5a1b9ab5f022d523439773e36ac2bb2425a8",
+ "0xe64f1c19876551d7fcafc0dc04d175bf43781f67ed0f68bc9d6c0b05bfb6e913",
+ "0xb2fd84dabe373607e40a08b5e81e6e2e3b114e303e6683fc5aa7c6f4c2d8d3cb",
+ "0xeae018f72b9aff41138ad78bf007473d9f0cebdf43a08a7f5122a06d05c9e41a",
+ "0x2a95a7b73a8e28684734d2e6c7802e89e787500fac3bdca8ee6337c29f112097",
+ "0xbd958770ade49e1c64c41e29f23af4ab865447e6cbca7c503258ac413227f5ea",
+ "0xc6c8c54a51bb09f64432b40e36e5efdb4956152c0c37d5c296188af466dacd7a",
+ "0xc7e4d04b8dd6491c1e17a43abe623837452e6cf66942ccb2e5b5ce04f76240d5"
+ ],
+ "hashOfTxHashes": "0x1181510f8850880c6512ba84973f570de679b6dd7d6a2673c8f3ee3164b38c63",
+ "hashOfLogHashes": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
+ "hashOfPositions": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
+ "hashOfFromAddresses": "0x3fb95664492db49a9c4f57e8aa4ee8fef44da97b4ba14cd9a1df9bca38e72961",
+ "HashForBlock": "0x6c20a02f2de954c3eec497f3afc390fd3e5f46690033712163b146bb16874dad"
+ },
+ {
+ "txHashes": [
+ "0xe408ad17e10f37365279d5966fbcd58fe6d11dba184c6fdacd6d33ebe950c334",
+ "0x5d4cc1caba2556b51813e2ac8d2ddc99dd57ac9cdeb6a24aec1bba190731f42b",
+ "0x5e86d43e7693471cfcb42eabc54a68a6b15f3a62e837e4d596a0ec68856e7493",
+ "0x30d791c89ac39d53ddaacfcceccc4bb94254d5aef89e82cb7472607b288cd955",
+ "0xd4b08bf89b28321bf937aedd5c601320ceb215c4b261411f0677eeb2045fba7c",
+ "0x4b19af47880b0e3210f6cee896c68ee7a50ad1a5705a28f2f5fb91f94a5ab2c3",
+ "0x53e260122beef7f1b158342517c2cb4d1f687f755ba0c9d17a17de48b5abcff0",
+ "0x4a29fe1effed49afb604fc521f8018b7ab524cae77953fc7b1ece969a5002449",
+ "0xed351aa2c65da3dbcab0f40e098284945cd8387f7ee17cccfa54c63ffe42d986",
+ "0xa088c5179dc084821e78fcf5c9c919b0afc588ef1e772d7e56e80bb14c7891fc",
+ "0x4b6f92186de0f92685bcc7d5ce76f354fb8bdb8fb42af797a95e930ef8047740",
+ "0x9166d9aa9fe71db1dcca6d0e9f4eaeaf62f5987bd1f3bd192f1f1e9fe8a61c5c",
+ "0x20d2aff42293b063dac160f72f28a4d50a356b28ab020edea517f495f03e5f34",
+ "0xfc3413d8b9a5a44b20a9b2bd778bc2a9d5309c897fb74320a067946282a2977b",
+ "0xd1a91cd524f06c71b92c8df87002bba7e2c5a10fd7e876f383040423bab6ae16",
+ "0xac02e1026769ee118cb82091c1a9ddf859ab91cd8e8711ed4a86d9b42830ded6",
+ "0x0dd6d66a03338e83d7789dc2ecd22e3fe90b39290feb827111e8b575f9589669",
+ "0x73248961e78aa90d28a2a225f54476580d4e45ec4a863ac664b254711421f2b8",
+ "0x40ffb1db14347b84fc4be1a9e20eade0bae32b1fa346a18a908b0ad4f6ec71a3"
+ ],
+ "hashOfTxHashes": "0x52b77f19ed8482dc71836c35e5afc787af4c91c845b2a69faecbcb29fc9fc905",
+ "hashOfLogHashes": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
+ "hashOfPositions": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
+ "hashOfFromAddresses": "0xbf40b5ca90779b817e50e8b4ae91c992c0fba6d0eef3fb23b52a076a1ea7e4ff",
+ "HashForBlock": "0x44ee6cd31dc7e7e80123f6bb3a64b00aa7ad0add4655fb026be8d53db72c83ad"
+ }
+ ],
+ "hashForAllBlocks": "0xa39af1a5682fd4462802511aee8e1c564e5c218d8cd2f6fda5ee9bb36d22ca6d",
+ "hashOfRootHashes": "0x609a61ed9c34be74036320ea49a390c4df31df88bf2e4b801babd8331480b103",
+ "timestampHashes": "0x99e954ed3802cb4418b96db5a9820eb88079c44e31c9ec455c762b2c68be598b",
+ "finalHash": "0x03add338a1b5fac261400153540f8266c971945823bfba1bdbaf6588fde40091"
+ }
+}
\ No newline at end of file
diff --git a/contracts/test/testData/FullLarge/output-file.json b/contracts/test/testData/FullLarge/output-file.json
new file mode 100644
index 000000000..b122fad87
--- /dev/null
+++ b/contracts/test/testData/FullLarge/output-file.json
@@ -0,0 +1,308 @@
+{
+ "proof": "0x212c3964774eb071fab45629ea97c0986a43cf96703d1a879fcc4c0c9d3b1a432e88b8c5783271091def99f2ddcf7f27d726ad66d16d6dbc0fa6410390a58feb2fa89e648ec72184dce7c1af6ec4f238125744801885a5d6e1f9e62e43c39abf27160f114a08714c4a8afaabe12326f8fd04efd6edd17eecbd8db7d39249d308292906a0f8f80c2850512674a0fdc8e0a95b91e0b71ab2ae7e6d59ddf2ad9f870a5e2592135fb09d848546b34d1e3321767428e8aac5afe660824c09ccfdbf5c0fa846a14d4527a6b9de863adf6212a00a0387100a36c776265920b1f8ffca8f0d95ebd0b851258f780ddc6729d50656710a7196ccfee1d0098b07de802b4d0211019f8371ae38be4b4a7a33f3eb4c203ec69e39a86da22d5443f091ff695574030bd4fabf668809f6dd1060bf289156b5b435a26915c1fa24bfa2bcabbb006017a7f20150e98d9ffaa77cd6cc03b533ac3928c96daf8d6c397bcb6519085a7816087cd7d84f7d8892f6b16842f18ca3bf34b07f05ea55b9f7cbdd20497c80c7219e4a2651fa28f103ab5c256ca0328f7c2b83382b62f7b679f64b7923b37e672e9e4ce6b01f51c225527613c59c1f42ad6cad4d1d27b0f25532ec2026350a6f07478ce7ad597e7ba666b355bb6bc79bcb7aae209449ca1f2dea9c18d7eaacb80dd19278336a6d828287ab347362c0ee28f1f9d38398b0bec380cd8d877d9bd00b61150fffcf3c7cc1a76d68b31d235cb12463c46b700fc4c18084eb0a9892102e48720daedb426a6699da6c56dc4fbf09cfaaedd143c91441e4aa8865b57e742ea607dc87aac7f34aa28bb868581cf5c4b29e054b26856b52bdc300ddd46ea106dbaa1b3e2dfa266a8670e2f695b95bb2fb224be1663d14356cc0188e8be1e42054fc15725d03c62d5b7f7dfa546772d25edda2bee1201a03e9f6b74ab10cb2123adec3f80e2aa792d9692a32ee16169b619568f88b902d1d75bf3af63d053717d1843d32b4b15189d0af7a99956c0993fc7a703e554b84a17341ba5da899162b5a7c75c4973aa7d3afd1ebcc97e9f13b6f57145cbc679bacbb98b2703035f11da13a576b9736081b11365bad8ca0760c2a31004faf8f3816752ffca10270ff1928f4ff06a35cc77aa10bbfa0717346e1873757f42b6405c41f41a0fb20f60228d29bf6c9342745014e360e4baabd2b3427f49742f8cce63e837061827df8ca2cd8aa1938903468c68b6d888b93ae7a2022fe4fc83e8668538bc480e1c356e304bca6c75d6611c7cb6752ce914ff709be06f356463d132fe2b1f9111b2f7c80",
+ "proverMode": "full-large",
+ "verifierIndex": 2,
+ "blocksData": [
+ {
+ "rlpEncodedTransactions": [
+ "0x02f9091382e70882054484962fc6ee85174876e800832625a094508ca82df566dcd1b0de8296e70a96332cd644ec80b908e4f4b476e100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000045161d5e6ce5e92df9502203fd8e60b26cb70894d99155576d7776057cf189577b3f762c86d9b7dcb0ccf3be280ac541f2d0514fcea664dadd7456cf50eaf2d2f10013acd8b6ab926aebce6479e1e69cba954d9a12280333a01764665a5753f2e465feaf9735a1a209af27b3961e604f09a79ed9ddc8cba4fd3f32446a2018bdbc0f876fee16158abb8ee62791d73979460eae03f4463c687662eb4ed55366f286fef0cc22ced3e884f6ada00b47ab4a643bbac88289e65a41ee741307eda80be8e8964030c3a8d5d5c81f99999a0b6d68a7b3af3a9a077c1d1cb7a61f0d1199089baf45c5dd81d359e4744b02f811ff6826d0db21c23686440c3c4019023691a394a7655f567ed5441ff236f1bd06db8fe26c9ceada60fd7f4b2180ad0db7833092bf2c52a1d0b4fe82a78bded7ee559ffb37f74de252e169ef907faa79cc84e888b947aad4534065024a5c3932e1ffa287fda0890fae04b45c2913c31fd992322973f916e0feaeee2180645f59b3336a07d7fdb2db52fdde27ccc544e2b030efa3db5e99efaab00e74314333b92bf5c358a61fd7e4ff920e8cf903866615adeaf2df544df269404bba4d4f94e87fc0f33d980511b4f3677eb337784bfb50209ae5f9fc6642c89a99698aec9767a936745e5538815221121ff20e1b6c89deeb292ea4eb17dbdd045924d3afcd4c418cf4f6a0a10c7231bbf852f2f4e18393f13cef6772e7e57ba3c74a3df64ae759e47be96b1185898570ce963d9c67acc30cfcb5ee5930478f657324e1427c76e49afd25111e0c9eec6842334ed75d0c5ba1c465342c6e75e0c5439f2fc0ea9761fe2b6890247f549ca4167425a953e642c31c9159b2d892edbee614e439766a7ddd8a66cd25bf9a45d2d1f580cf91c77386c1df74a4d98d38af559dc53177503e9157c6df8c8889d7f47bcee62749efb5b0614cce452e428e4780faad5ab385f77ecb89686c1c026927a9607bcd368c1212b0ee1dae5b27bd4796eb07b7cbb65accda47b8fc2336d53611ba5a880e3a84b41a65a9ff6e91c0f8ee8e00b506e81a8eacefc803d9474edbe0380898f324d49ea68fa2c7b2f5d52ee5e47308a0b2788937bb1195f5603845b07eeaad81b6d65f31028e614b18f73872399f6b643c8e33eb88eabdce099ff3eacd42193ea732a22826c6e79c13acb66deed9ca11ce95ffcd356a25e4f3e4b60f7fa36cd40d7171589fe695da70d6c976a4d79a84137713bd63eed6e5622f351faee3169cbb59c327d02471a720e7648c4c47a6592cc5d7c148fefbfeafd91d8ad350e14c691680e8fd47baf17f82b0aab2c3dbd23c76434c7b4e07c1e1c21a98886e2c6e866b8a60af2c75df6e29e0b40000070c6710f29e1b4e0c2e18cb2cf68344c8e259ee85408585f12b4ab7316f36b3c31fd1097faa1a036b6f1a74015420b3eca68daa03002ab06d810efce532fcd0996960288a1be9a2a43ac828b3ab4538853860a9bfebd8b7c0f65cdc54138137aed477733cc9726b748679d13f516150ccf39410795f32517bfbec0f3ed4dcfe2ef70029e35cacba809bbbc2a6c82543b19c1c1bff7fab52327ac00465b35015d9baaaec5a5aba682f9bef177e216e9e77319aadf64d7790878c01cef76b23eba1ed55f16ecfea6decde45118d2efbe4c22fb630d1c27742f4723148a934c1a97454fbf3fd603ef5a3f7d38ce4ae87bad4c25523afd669a112750bfaebf85f2e465cfbfb3bf8bad1b558ea91c556145eb87ec02f984d8c2f44369c14bf265536a5f32d36711add8c3ae841ce268b0c52533c64e3d841c9d4b63658f8e0ad32bb512aa7fb77f3baf72ff71bfe214676fa5650eae2971d0ce43b49abf645430874e5fc9dccf668217040fdf3cad9ab0140915c65fd5e9816743a249984e91746c5f0670d7fad7d642158142d570a7ec9e761f37bbc7ea7245befdeda160dc55ef1e2512a16ef5a3668ad785acf4fa836902aeb07ae03ceb24267beda32f3cfb7f5a9929434a3a9f6ddf7e4cfaecf5b071578c047d52a857dd5b30077d6a874892558f8798ee1c335aec8c8e47918ebec1f2a6c1cedc7b4d8185fc041defbef038d3b379ad7e84a047c132a49d0a28e86adf4d11d50da3a8875e89291558805f75e43c02645f08c8444184b87ab93d644b0051e7a6a756a059a97e91a82349d8907a33c1c90daa6e66deefb448b2cde8fe121bfc8b582c556527a7bfb11fde9b4bc645ad35369d630fe3593e020630231687d04cc5c81317eee0976e63f63644f20dd993203ec0ebf3be9af1b3a2d9ee3a779202d69d3d27b7aed886479604731caea8e3c8d878f030acb22a92fde9856ca342222759a9ad734e4a280c23573a003230f1c515e66c531beb518c2c4786d64c46c49854c2fd17722e1a82c08b3a35bc59f7771f29c4bf4e6a18ddebb66beac48ad7bda9122be176ded174b963bb4f3b8b13efdb9bef4608b9f3fdec2222b683dfa6bc41f247f76b2644dfe71e95f5b0b46aee8350e95a3afb46262a20d0e99e9216a317a571a319018d134ef83c9e335af1e46d0eae1377320c8036394d7218341eb4c775a97c2ca8817d1d0febd1e48604e33b395a13a9a36f1151fab3b1470bcfd924e6442ddc8cb7284bb159abef1d2ce5f23c3d892eb74bd31f02a8754aaf9d07476724c70349a8fd5d8d370579461052a9aae0072e79b5fb5a8b5c0f0ee747747554bb1608f7670b9e14b680ce360caa6097ccee08710816b325ac9d18038eab16b16de7a4d727f9dedb9a145a39952e31b264c136bd46d8b45f8683a2af9f27006325ad9e342bafb6ac4756485a79d8cf2d63d72a2284d1118bd195098891bc23831b605b47a9c8665d9bb7b7eb7b5966bc37a56c53f6fcbe28953add2b9fb7d6bf7d9d480563e0cd07f05226c9c8a3c3496cebed0000aced1c27ad9e3494362cd6da3d8686dde841fcc724d830e71160c0359393d089ae3e49f412b567823e5efdb4f43d246e35b2e73620a8b0d6c3f5f45709180c6ebeb48211c422f65a5374b5914b0d3fa1ca03f1960c7a50b6ddba710de8859c132e48e57c57485d937c11c4648fcb2b8a636f3b2a1e27ced0e081c57e8e3f50b5c78481fe743daafd2d15cdfc0",
+ "0x02f582e708823ed185012a05f200852e90edd0008252089452578e1fe11f92618dd178629156de2307b1065d880234e1a85749806180c0",
+ "0x02f86e82e70813846ba55515846ba55515827223947d43aabc515c356145049227cee54b608342c0ad80b844095ea7b300000000000000000000000080e38291e06339d10aab483c65695d004dbd5c690000000000000000000000000000000000000000000000009275143d32c82d5dc0",
+ "0x02f84d82e70808846ba55515846ba55515828cc794e5d7c2a44ffddf6b295a15c148167daaaf5cf34f80a42e1a7d4d00000000000000000000000000000000000000000000000001866c3674ca28e7c0",
+ "0x02f9029682e70801846ba55515846ba55515830282209480e38291e06339d10aab483c65695d004dbd5c69861b48eb57e000b902642cc4081e000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000fcb9d92cf386180000000000000000000000000000000000000000000000000000000064bc7dcb000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001b48eb57e0000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000046ec4bb184528c3aee6f1419e11b28a97f33d4830000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f000000000000000000000000126824925a67f98dca1eb9d92d78d51c99f3a42700000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f901d082e70806846548b1a4846548b1ae8307c07494438670d41d5118003b2f42cc0466fbadd760dbf480b901a4ea5406320000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f000000000000000000000000000000000000000000000000000000000000012cfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed928fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed9dcfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed8ecfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed9a000000000000000000000000000000000000000000000000093a7cafa8b08e3b30000000000000000000000000000000000000000000000000011c37937e0800000000000000000000000000000000000000000000000000046823c1fd558d2f60000000000000000000000000000000000000000000000000007351f791fb3be000000000000000000000000a8a71d5fb2d5fbc8f31799c5cdd8ae67722885a50000000000000000000000000000000000000000000000000000000064bc5393c0",
+ "0x02f86f82e70809846548b1a4846548b1ae8301042f947d43aabc515c356145049227cee54b608342c0ad80b844095ea7b3000000000000000000000000438670d41d5118003b2f42cc0466fbadd760dbf40000000000000000000000000000000000000000000000047049d62f86d62000c0",
+ "0x02f8cf82e7080b846548b1a4846548b1ae8303374994b29caa2cb1feb7f4ccaa9dd9b8ad2022eaca6ec380b8a46170b162000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000413f019ead393d9d0c66745b08e818fe13179bd3da2d6559b2bb9c40aca9618c364ecece8c8fc0d89d0e5897210ae3b5521f49cbb178e00ee568be62d583edceb51b00000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013082e7080e846548b1a4846548b1ae8302cdc594c66149996d0263c0b42d3bc05e50db88658106ce80b9010418cbafe500000000000000000000000000000000000000000000000098a7d9b8314c0000000000000000000000000000000000000000000000000000001420824645e5d100000000000000000000000000000000000000000000000000000000000000a000000000000000000000000078ea6f35af55843f9d1d654f4a076ef435fa34690000000000000000000000000000000000000000000000000000000064bc539f00000000000000000000000000000000000000000000000000000000000000020000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34fc0",
+ "0x02f9013082e7080e846548b1a4846548b1ae8302cdc594c66149996d0263c0b42d3bc05e50db88658106ce80b9010418cbafe5000000000000000000000000000000000000000000000000a688906bd8b000000000000000000000000000000000000000000000000000000015f4b85f94d69f00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000005029056a3581629953feba7cba2446b6057295160000000000000000000000000000000000000000000000000000000064bc53a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34fc0",
+ "0x02f9013082e7080e846548b1a4846548b1ae8302cdc594c66149996d0263c0b42d3bc05e50db88658106ce80b9010418cbafe50000000000000000000000000000000000000000000000009cd1aa2149ea00000000000000000000000000000000000000000000000000000014acf9cb1dc31100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000326b931b50a190704341c4f832ebd7a75f4e86290000000000000000000000000000000000000000000000000000000064bc539f00000000000000000000000000000000000000000000000000000000000000020000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34fc0",
+ "0x02f9013082e7080e846548b1a4846548b1ae8302cdc594c66149996d0263c0b42d3bc05e50db88658106ce80b9010418cbafe50000000000000000000000000000000000000000000000009f98351204fe000000000000000000000000000000000000000000000000000000150a9e61f0246600000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000d7c0fb08b29ea8bbca31b51d6d7bebadf288e2af0000000000000000000000000000000000000000000000000000000064bc539f00000000000000000000000000000000000000000000000000000000000000020000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34fc0",
+ "0x02f9023782e70816846548b1a4846548b1ae83054dfe94438670d41d5118003b2f42cc0466fbadd760dbf4871d2b46f19c59e6b90204ac9650d800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000104c238a3a30000000000000000000000000000000000000000000000000000000000000fcbfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeccf8fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed0e0000000000000000000000000000000000000000000000001f9129260ed919f98000000000000000000000000000000000000000000000000001d2b46f19c59e6000000000000000000000000000000000000000000000001d1781783df86e0cf000000000000000000000000000000000000000000000000001902f4b0f0b9cf0000000000000000000000000000000000000000000000000000000064bc53930000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041faa413300000000000000000000000000000000000000000000000000000000c0",
+ "0x02ed82e70880846548b1a4846548b1ae82b22494cce9d3f392c135dc038b147ca73ec496f7f89d938084183ff085c0",
+ "0x02f86e82e70803846548b1a4846548b1ae82fb2a9466627f389ae46d881773b7131139b2411980e09e80b844095ea7b3000000000000000000000000438670d41d5118003b2f42cc0466fbadd760dbf4000000000000000000000000000000000000000000000000000000000046b7f0c0",
+ "0x02f9013082e7080f846548b1a4846548b1ae8302cdc594c66149996d0263c0b42d3bc05e50db88658106ce80b9010418cbafe50000000000000000000000000000000000000000000000009f98351204fe000000000000000000000000000000000000000000000000000000150a9e61f0246600000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000cc6fc47b6a3a03b41f43fd33c699a1ac32e3b6910000000000000000000000000000000000000000000000000000000064bc53a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34fc0",
+ "0x02f9013082e7080d846548b1a4846548b1ae8302cdc594c66149996d0263c0b42d3bc05e50db88658106ce80b9010418cbafe5000000000000000000000000000000000000000000000000a25ec002c012000000000000000000000000000000000000000000000000000000156842a209508f00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000002183ba8662f9f0e1bebffab11e31ba8cae53bcec0000000000000000000000000000000000000000000000000000000064bc539f00000000000000000000000000000000000000000000000000000000000000020000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34fc0",
+ "0x02ed82e70880846548b1a4846548b1ae82b22494cce9d3f392c135dc038b147ca73ec496f7f89d938084183ff085c0",
+ "0x02f86e82e7080b846548b1a4846548b1ae82b7679466627f389ae46d881773b7131139b2411980e09e80b844095ea7b3000000000000000000000000272e156df8da513c69cb41cc7a99185d53f926bb000000000000000000000000000000000000000000000000000000001ee98853c0",
+ "0x02f86f82e7080b846548b1a4846548b1ae8301042f947d43aabc515c356145049227cee54b608342c0ad80b844095ea7b3000000000000000000000000438670d41d5118003b2f42cc0466fbadd760dbf4ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0",
+ "0x02f082e7080f84623ed54584623ed54f8252089445a318273749d6eb00f5f6ca3bc7cd3de26d642a87b8bdb97852000280c0"
+ ],
+ "l2ToL1MsgHashes": [],
+ "timestamp": 1690062599,
+ "rootHash": "0x290c378f1d39464c72c71773d84030cbf3d354bca03f92caae4139a06876790f",
+ "fromAddresses": "0xc1c6b09d1eb6fca0ff3ca11027e5bc4aedb47f6780c67432656d59144ceff962e8faf8926599bcf86810dba12317406828d64b07d34ad22338644bb3c4f53c1aeb4e8f12b2c427e7a4505cd189be3392126824925a67f98dca1eb9d92d78d51c99f3a427a8a71d5fb2d5fbc8f31799c5cdd8ae67722885a552e085691b4ae78da51187a5e9d8381f08fe2bd392b1a1c7c7542e23a65e528d2d58a891c9aa475978ea6f35af55843f9d1d654f4a076ef435fa34695029056a3581629953feba7cba2446b605729516326b931b50a190704341c4f832ebd7a75f4e8629d7c0fb08b29ea8bbca31b51d6d7bebadf288e2af4c9728684fd37254a82956ad9a8a87bbd938364a7a145a31686980565976eae84024d4ca9c06bda82d584a6d566ea2cee64ea18dddbcddacbb7f681fcc6fc47b6a3a03b41f43fd33c699a1ac32e3b6912183ba8662f9f0e1bebffab11e31ba8cae53bcec359084e2583e835807164c0f864ecff655d1849f4de89c2ae04a31cc2dba3e2da11a38ddc43682b90ff2e6c1d14ce8a94e74d0eb9d6f2536363aeb409232224978f9b83b580ba028bdab92d7976ab41f",
+ "batchReceptionIndices": []
+ },
+ {
+ "rlpEncodedTransactions": [
+ "0x02f9013282e70882b240846d0182b0846d0182be8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000090ae52f244688d687b693d99769e455a38de9b2b00000000000000000000000090ae52f244688d687b693d99769e455a38de9b2b00000000000000000000000000000000000000000000000000013811a83d9bc0000000000000000000000000000000000000000000000000002aa1efb94e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd090000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b241846d0182b0846d0182be8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000052df1b28c2afc2cbb0b320fd787a7844c6e580cd00000000000000000000000052df1b28c2afc2cbb0b320fd787a7844c6e580cd00000000000000000000000000000000000000000000000000013811a83d9bc0000000000000000000000000000000000000000000000000002aa1efb94e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd0a0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b242846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000000bda323c78a069cee9d2265ced3bcfa1332ba7070000000000000000000000000bda323c78a069cee9d2265ced3bcfa1332ba7070000000000000000000000000000000000000000000000000002e50893ba5a59000000000000000000000000000000000000000000000000001b0d7ea876e025000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd0b0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b243846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000b9652a464ee262d5aedc9189fcb59ea04441b21a000000000000000000000000b9652a464ee262d5aedc9189fcb59ea04441b21a00000000000000000000000000000000000000000000000000031b636ca3611e000000000000000000000000000000000000000000000000001a66ab71d3c9ad000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd0c0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b244846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000d1a6dc9b8762fe8c93c5ac103a1e365cc9fb5a06000000000000000000000000d1a6dc9b8762fe8c93c5ac103a1e365cc9fb5a060000000000000000000000000000000000000000000000000003391f90d6498f000000000000000000000000000000000000000000000000001ab250d3446d10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd0e0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b245846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000a9b13af6181ff98c9eeaf9ca46095ee2f916f832000000000000000000000000a9b13af6181ff98c9eeaf9ca46095ee2f916f83200000000000000000000000000000000000000000000000000028c843d42655a000000000000000000000000000000000000000000000000001a62ddacb258dd000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd0d0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b246846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000004acc7828d76abf4f84105f7c826a0d52b00ba38a0000000000000000000000004acc7828d76abf4f84105f7c826a0d52b00ba38a0000000000000000000000000000000000000000000000000002827a4d2c36e8000000000000000000000000000000000000000000000000001b5434999b29e6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd100000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b247846d0182b0846d0182be8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000027bc8621471d8d84475a211fb7556c158958d0d200000000000000000000000027bc8621471d8d84475a211fb7556c158958d0d20000000000000000000000000000000000000000000000000001429fc0610640000000000000000000000000000000000000000000000000001c110215b9c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd110000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b248846d0182b0846d0182be8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000007e4e0c3be87aef0e846162c40d96c7ffbcf1ca130000000000000000000000007e4e0c3be87aef0e846162c40d96c7ffbcf1ca1300000000000000000000000000000000000000000000000000013811a83d9bc000000000000000000000000000000000000000000000000000f8b0a10e470000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd130000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b249846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000ded87809191d1ce529b875d81478f08c4e7e1028000000000000000000000000ded87809191d1ce529b875d81478f08c4e7e10280000000000000000000000000000000000000000000000000001967d1439851800000000000000000000000000000000000000000000000000523f1929090ebc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd120000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b24a846d0182b0846d0182be8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000004f579b878eebf064b1d1f1d8b8032f869ec650190000000000000000000000004f579b878eebf064b1d1f1d8b8032f869ec6501900000000000000000000000000000000000000000000000000013e2428d4b4e00000000000000000000000000000000000000000000000000017cd9d4ffec000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd140000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b24b846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000e512e419213e04728cf87c784620cd1bb0e91fdc000000000000000000000000e512e419213e04728cf87c784620cd1bb0e91fdc00000000000000000000000000000000000000000000000000025ab9cef6d3b6000000000000000000000000000000000000000000000000001c3ef9184c62bb000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd190000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b24c846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000004667317607f05721a60b69e2da3254b58b4fa2150000000000000000000000004667317607f05721a60b69e2da3254b58b4fa21500000000000000000000000000000000000000000000000000027612ae8381aa000000000000000000000000000000000000000000000000001b3ff4d6abb3dc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd170000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b24d846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000a91a200fd2959c0a3bb530eb6e14c5376fadcce3000000000000000000000000a91a200fd2959c0a3bb530eb6e14c5376fadcce300000000000000000000000000000000000000000000000000024479a76f5533000000000000000000000000000000000000000000000000001aedb721196262000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd180000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b24e846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000a9b49485282fe1d16a1e7dffab0378fd2922e27f000000000000000000000000a9b49485282fe1d16a1e7dffab0378fd2922e27f000000000000000000000000000000000000000000000000000233e4626489ee000000000000000000000000000000000000000000000000001b36c2a7ac1d62000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd150000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b24f846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000059ad5ae25a4c2e40ef2fb5df27db491af72d6d4200000000000000000000000059ad5ae25a4c2e40ef2fb5df27db491af72d6d420000000000000000000000000000000000000000000000000002b7ebd3b6c5f3000000000000000000000000000000000000000000000000001c6d36103d6026000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd160000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b250846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000c754a76208d323fabfb5e60b5cd6602673236bb3000000000000000000000000c754a76208d323fabfb5e60b5cd6602673236bb300000000000000000000000000000000000000000000000000022fbb5512f782000000000000000000000000000000000000000000000000001b7679a96d2844000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd1a0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b251846d0182b0846d0182be8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000001ed2d8eebcd1d0319a4e5374bdbed288589f00a10000000000000000000000001ed2d8eebcd1d0319a4e5374bdbed288589f00a10000000000000000000000000000000000000000000000000002691003e5e8d6000000000000000000000000000000000000000000000000001b10359ee7ad8f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd1e0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b252846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000dfa435f8df3036bab2e1ffe4923840178ea54c3e000000000000000000000000dfa435f8df3036bab2e1ffe4923840178ea54c3e0000000000000000000000000000000000000000000000000002a991e6a46a9a000000000000000000000000000000000000000000000000001bff45bf1da75e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd1d0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b253846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000feffbb43619cad27dc96a60618495be1a8882f5b000000000000000000000000feffbb43619cad27dc96a60618495be1a8882f5b00000000000000000000000000000000000000000000000000026a801a888074000000000000000000000000000000000000000000000000001b103c21e6c968000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd1b0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b254846d0182b0846d0182be8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000e6f9e9783211e6d22108bf30d448e27694479d38000000000000000000000000e6f9e9783211e6d22108bf30d448e27694479d3800000000000000000000000000000000000000000000000000016b3f8f9ae760000000000000000000000000000000000000000000000000001cc6e836ae4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd1c0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b255846d0182b0846d0182be8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000dae2f9005c7c2bb2a29bf4eee6ee8c29cc3af7ff000000000000000000000000dae2f9005c7c2bb2a29bf4eee6ee8c29cc3af7ff000000000000000000000000000000000000000000000000000237fe04de55fc000000000000000000000000000000000000000000000000001bf2081e5ca582000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd1f0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b256846d0182b0846d0182be8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000b25cb5c1c5ac5b59971e907ed97e90364b760e30000000000000000000000000b25cb5c1c5ac5b59971e907ed97e90364b760e3000000000000000000000000000000000000000000000000000016b3f8f9ae760000000000000000000000000000000000000000000000000001c3e7b9df6e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd200000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b257846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000004d58159ed0e1fa7992970875e7860bb9755d4e040000000000000000000000004d58159ed0e1fa7992970875e7860bb9755d4e04000000000000000000000000000000000000000000000000000228d889a3d34a000000000000000000000000000000000000000000000000001b3c16d950ce97000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd210000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b258846d0182b0846d0182be8301361d94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000ee99f3fb8744dd05cbbd87104b7f088d194992ae000000000000000000000000ee99f3fb8744dd05cbbd87104b7f088d194992ae00000000000000000000000000000000000000000000000000016b3f8f9ae760000000000000000000000000000000000000000000000000016345785d8a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd230000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b259846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000076d696f8f3f5904aecf0f8982b82f88d80228f6700000000000000000000000076d696f8f3f5904aecf0f8982b82f88d80228f6700000000000000000000000000000000000000000000000000029a2f0df23dd9000000000000000000000000000000000000000000000000001c63e6326e3509000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd220000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b25a846d0182b0846d0182be8301361194508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000f2ed2042f3bf1c9e9ec3de5a79d6b8334e498d20000000000000000000000000f2ed2042f3bf1c9e9ec3de5a79d6b8334e498d2000000000000000000000000000000000000000000000000000016b3f8f9ae76000000000000000000000000000000000000000000000000000470de4df820000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd250000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b25b846d0182b0846d0182be8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000006bd99000b5874c02e666ced80905f038024296900000000000000000000000006bd99000b5874c02e666ced80905f03802429690000000000000000000000000000000000000000000000000000299cc8b064176000000000000000000000000000000000000000000000000001a7abad9e76168000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd240000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b25c846d0182b0846d0182be8301993a94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000008a8625919bfa16c58f5c3aa900c616be91955c520000000000000000000000008a8625919bfa16c58f5c3aa900c616be91955c5200000000000000000000000000000000000000000000000000016b3f8f9ae760000000000000000000000000000000000000000000000000001ff973cafa8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd270000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b25d846d0182b0846d0182be8301361194508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000000a6f2f5ad8954edbadfdb508e599cd26ab668b970000000000000000000000000a6f2f5ad8954edbadfdb508e599cd26ab668b97000000000000000000000000000000000000000000000000000155aa30e6ad80000000000000000000000000000000000000000000000000002386f26fc10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd290000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b25e846d0182b0846d0182be8301361d94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000c1ea3db638495d90bb778863ff5c2aaccfbbf421000000000000000000000000c1ea3db638495d90bb778863ff5c2aaccfbbf42100000000000000000000000000000000000000000000000000016b3f8f9ae76000000000000000000000000000000000000000000000000000c3663566a58000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd2a0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f482e708823ed285012a05f200852e90edd000825208949d0e0c96288a3ebdda1bd73523aeb8e262c6e679872714711487800580c0",
+ "0x02f482e70882402185012a05f200852e90edd0008252089468ad1fa00cb9d499b73e85c6449766374463b6b2872386f26fc1080980c0",
+ "0x02f482e708823ed385012a05f200852e90edd00082520894fa8d660cfa214e3071b915d5bb88c75834e9a15587470de4df82005080c0",
+ "0x02f84d82e70808846ba55515846ba55515828cbb94e5d7c2a44ffddf6b295a15c148167daaaf5cf34f80a42e1a7d4d000000000000000000000000000000000000000000000000000d51e75245751bc0",
+ "0x02f86e82e7080b846ba55515846ba5551582b4fb947d43aabc515c356145049227cee54b608342c0ad80b844095ea7b300000000000000000000000080e38291e06339d10aab483c65695d004dbd5c690000000000000000000000000000000000000000000000014546d1a4322aca3ac0",
+ "0x02f9029782e70801846ba55515846ba5551583023e759480e38291e06339d10aab483c65695d004dbd5c69874c1522a7a7ae9cb902642cc4081e000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000224ec2fcd3bcafda90000000000000000000000000000000000000000000000000000000064bc7ded0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004c1522a7a7ae9c000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000007f72e0d8e9abf9133a92322b8b50bd8e0f9dcfcb0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f000000000000000000000000de705471bb4414bfdabe09f52665914ed0e5725400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f86e82e7080e846548b1a4846548b1ae82b3e494e5d7c2a44ffddf6b295a15c148167daaaf5cf34f80b844095ea7b3000000000000000000000000272e156df8da513c69cb41cc7a99185d53f926bb000000000000000000000000000000000000000000000000001a711930b89000c0",
+ "0x02f86e82e70838846548b1a4846548b1ae82b4fb947d43aabc515c356145049227cee54b608342c0ad80b844095ea7b300000000000000000000000080e38291e06339d10aab483c65695d004dbd5c6900000000000000000000000000000000000000000000000c31f1cf83c3d8fb3ec0",
+ "0x02f482e7080a84623ed54584623ed54f83014cab94a02573c4ad15c16b48f10842aac9c9ea405b65a386d12f0c4c6000841249c58bc0",
+ "0x02f582e7080184623ed54584623ed54f83012779943c5b31b158dcaba76df46bd853c8af3ccf0f002287016bcc41e90000841249c58bc0",
+ "0x02f9027082e7080784623ed54584623ed54f8302d78c94272e156df8da513c69cb41cc7a99185d53f926bb80b90244ac9650d800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000104a8c9ed670000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f000000000000000000000000000000000000000000000000000000000000012c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064bc53ab0000000000000000000000000000000000000000000000001f9a0ef077f3ff15000000000000000000000000000000000000000000000000000444a1b61145790000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044bac37ef7000000000000000000000000000000000000000000000000000444a1b611457900000000000000000000000017cec49895644f84c50809d874e22feb923553d800000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9023782e7084284623ed54584623ed54f83035d6494272e156df8da513c69cb41cc7a99185d53f926bb8701e794b458ec96b90204ac9650d800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000104bfba6b22000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f0000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000000000000000000000000000000000000000012c000000000000000000000000a6dff484a46f90e5d03ff91e766ac70cf1e0eecb0000000000000000000000000000000000000000000000000000000064bc53870000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000001e794b458ec9600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041faa413300000000000000000000000000000000000000000000000000000000c0",
+ "0x02f901d082e7080484623ed54584623ed54f8307359694438670d41d5118003b2f42cc0466fbadd760dbf480b901a4ea5406320000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f000000000000000000000000000000000000000000000000000000000000012cfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed928fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed9dcfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed8ecfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed9a000000000000000000000000000000000000000000000000032c969648a3f809200000000000000000000000000000000000000000000000000060a24181e40000000000000000000000000000000000000000000000000001864ecab95235e0600000000000000000000000000000000000000000000000000026db13ab8909c00000000000000000000000038022d79892c57761a67ab251c7d70a55dbc87120000000000000000000000000000000000000000000000000000000064bc53abc0",
+ "0x02f8f782e7081084623ed54584623ed54f8302cbbc94c66149996d0263c0b42d3bc05e50db88658106ce8803a2956f887d21acb8c4f305d7190000000000000000000000009201f3b9dfab7c13cd659ac5695d12d605b5f1e600000000000000000000000000000000000000000000004dea22ce324f339a3a00000000000000000000000000000000000000000000004d8667c05d93ecf1c6000000000000000000000000000000000000000000000000039dee49db01a039000000000000000000000000633eb62c0a2a7c6ff9d03f801b1adfa5f02cc55b0000000000000000000000000000000000000000000000000000000064bc538cc0",
+ "0x02f86e82e7081384623ed54584623ed54f82b4fb945471ea8f739dd37e9b81be9c5c77754d8aa953e480b844095ea7b3000000000000000000000000272e156df8da513c69cb41cc7a99185d53f926bb0000000000000000000000000000000000000000000000000a1a9ce0f703af0ac0"
+ ],
+ "l2ToL1MsgHashes": [],
+ "timestamp": 1690062611,
+ "rootHash": "0x1f00ce5b9e15ff5f9cbcba3ab213ca2c22dabecbe5f8a902553aedef133152b3",
+ "fromAddresses": "0x46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b80c67432656d59144ceff962e8faf8926599bcf8e4edb277e41dc89ab076a1f049f4a3efa700bce880c67432656d59144ceff962e8faf8926599bcf827d967c1a5636fcea985f696525bb4f911dd5c59793560c4e66c450545bc54271f84b17d989c5ecbde705471bb4414bfdabe09f52665914ed0e57254d59c3b026e9abef3f744491462d369e4c27040b312e7301eef23a02c5162dce59c0b8ba264dc92e46abc316192b43dc64c79ec841ac9546f793022c420bae152846374d7572c1fcfe182e6bb5cfcb13117cec49895644f84c50809d874e22feb923553d8a6dff484a46f90e5d03ff91e766ac70cf1e0eecb38022d79892c57761a67ab251c7d70a55dbc8712633eb62c0a2a7c6ff9d03f801b1adfa5f02cc55b5636d6f8810951d686a22da016654f22efdda85e",
+ "batchReceptionIndices": []
+ },
+ {
+ "rlpEncodedTransactions": [
+ "0x02f9013282e70882b25f846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000007fbebf89ec31747099485233aacef5e3f39fd5950000000000000000000000007fbebf89ec31747099485233aacef5e3f39fd59500000000000000000000000000000000000000000000000000024523c543d459000000000000000000000000000000000000000000000000001b60f3f6387fe2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd260000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b260846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000a885747c9baade27c1a57d5747ad128e3d069bb5000000000000000000000000a885747c9baade27c1a57d5747ad128e3d069bb50000000000000000000000000000000000000000000000000002858ff455d411000000000000000000000000000000000000000000000000001b7919966d30ba000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd2b0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b261846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000e6dbe9a315aa775a52a749923accbc752665a2db000000000000000000000000e6dbe9a315aa775a52a749923accbc752665a2db0000000000000000000000000000000000000000000000000002a19565cc15f9000000000000000000000000000000000000000000000000001d2b3e94606485000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd2c0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b262846d0182b0846d0182be8301993a94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000001f657297fbe86fb4bf3ebe00d47a7f047cf5e8f40000000000000000000000001f657297fbe86fb4bf3ebe00d47a7f047cf5e8f400000000000000000000000000000000000000000000000000014d8f7e915b2000000000000000000000000000000000000000000000000000205466db74c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd2d0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b263846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000079e8da554811076d8df8975c2175c696c438592b00000000000000000000000079e8da554811076d8df8975c2175c696c438592b00000000000000000000000000000000000000000000000000032e61fb77d53b000000000000000000000000000000000000000000000000001bf05f22026dd8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd2f0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b264846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000084802571e30720a5dde1af48b84915d0a7d9d2a900000000000000000000000084802571e30720a5dde1af48b84915d0a7d9d2a90000000000000000000000000000000000000000000000000002c4c9673ddda2000000000000000000000000000000000000000000000000001b39ac02dbb9c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd2e0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b265846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000007fa3116b528f8941e6e8530e4a36d0a9d80574db0000000000000000000000007fa3116b528f8941e6e8530e4a36d0a9d80574db0000000000000000000000000000000000000000000000000003118f508e1ad3000000000000000000000000000000000000000000000000001ba80bb438c2f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd300000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b266846d0182b0846d0182be8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000a5d3299a38b5d0020a1690f11f8450c9e197d654000000000000000000000000a5d3299a38b5d0020a1690f11f8450c9e197d65400000000000000000000000000000000000000000000000000016b3f8f9ae760000000000000000000000000000000000000000000000000001cc6e836ae4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd330000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b267846d0182b0846d0182be8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000078874c29e4a3de39618e9bc39a239bd008c6472e00000000000000000000000078874c29e4a3de39618e9bc39a239bd008c6472e00000000000000000000000000000000000000000000000000014d8f7e915b20000000000000000000000000000000000000000000000000001d7cce57a2c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd320000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b268846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000570e9127026d8764bf8402fe10bfd7dcbb665ed1000000000000000000000000570e9127026d8764bf8402fe10bfd7dcbb665ed100000000000000000000000000000000000000000000000000028aaaa249f44e000000000000000000000000000000000000000000000000001c0b062dd167a6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd310000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b269846d0182b0846d0182be8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000d33e335e680198a0c141b8ae5769498a000fc998000000000000000000000000d33e335e680198a0c141b8ae5769498a000fc9980000000000000000000000000000000000000000000000000002f55c5d4b2dc2000000000000000000000000000000000000000000000000001be650d1ea042c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd340000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b26a846d0182b0846d0182be8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000004f6b45db2da225dc9dfa29eb258d3d63018c72060000000000000000000000004f6b45db2da225dc9dfa29eb258d3d63018c72060000000000000000000000000000000000000000000000000001429fc0610640000000000000000000000000000000000000000000000000002081e063b1e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd360000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b26b846d0182b0846d0182be8301993a94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000072d0a5f5f802ca3950d9ff8abf467e6b1465d43900000000000000000000000072d0a5f5f802ca3950d9ff8abf467e6b1465d4390000000000000000000000000000000000000000000000000001438dbfe44300000000000000000000000000000000000000000000000000001c6bf526340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd350000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b26c846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000051870c7d99f761b9297433be7256ea24290a0bbd00000000000000000000000051870c7d99f761b9297433be7256ea24290a0bbd00000000000000000000000000000000000000000000000000024e14ee9dad98000000000000000000000000000000000000000000000000001c058614b05ed4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd370000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b26d846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000cc790b22ef435b76da0bb8d7c239fbf919655761000000000000000000000000cc790b22ef435b76da0bb8d7c239fbf919655761000000000000000000000000000000000000000000000000000298197090db88000000000000000000000000000000000000000000000000001cb13f9084f640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd380000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b26e846d1e961f846d1e962d8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000001e9a07ffcc98a932aa57fa57407833ec0adc6bb00000000000000000000000001e9a07ffcc98a932aa57fa57407833ec0adc6bb0000000000000000000000000000000000000000000000000002f0bfa7b63dae000000000000000000000000000000000000000000000000001c9da97501e879000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd390000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b26f846d1e961f846d1e962d8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000876199f2d7a3463179dc26c770fccb378a1dad22000000000000000000000000876199f2d7a3463179dc26c770fccb378a1dad220000000000000000000000000000000000000000000000000002f2078c9f9f67000000000000000000000000000000000000000000000000001c16b42e7835d2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd3b0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b270846d1e961f846d1e962d8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000a4734b099118455730d1a845dbdfd0725dc6a61a000000000000000000000000a4734b099118455730d1a845dbdfd0725dc6a61a0000000000000000000000000000000000000000000000000002fef25c863b85000000000000000000000000000000000000000000000000001c5f4e94f95862000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd3c0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b271846d1e961f846d1e962d8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000a211bd69c8a1400cb429a1ff92ad292489dc7f62000000000000000000000000a211bd69c8a1400cb429a1ff92ad292489dc7f620000000000000000000000000000000000000000000000000001429fc0610640000000000000000000000000000000000000000000000000000e35fa931a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd3d0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b272846d1e961f846d1e962d8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000c2f1d21cd496bea3a8361daaee75101df4d27ea7000000000000000000000000c2f1d21cd496bea3a8361daaee75101df4d27ea70000000000000000000000000000000000000000000000000001f0b2c94f77c0000000000000000000000000000000000000000000000000002e2f6e5e148000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd3e0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b273846d1e961f846d1e962d8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000b466fd02e3e151bcbce9c97fb0a9395889114df6000000000000000000000000b466fd02e3e151bcbce9c97fb0a9395889114df60000000000000000000000000000000000000000000000000002ddeda9078707000000000000000000000000000000000000000000000000001d11453af6ce96000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd3a0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b274846d1e961f846d1e962d8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000ac2314f552fe74262d6238bd4a6af9b910bd92b7000000000000000000000000ac2314f552fe74262d6238bd4a6af9b910bd92b70000000000000000000000000000000000000000000000000002c8d66ed8a114000000000000000000000000000000000000000000000000001ae9d2ef83c6e8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd3f0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b275846d1e961f846d1e962d8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000a4fe2ec308b39ded3fa9528efb798cd13a4f267e000000000000000000000000a4fe2ec308b39ded3fa9528efb798cd13a4f267e000000000000000000000000000000000000000000000000000300330dd90090000000000000000000000000000000000000000000000000001aa0fa68dac85c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd400000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b276846d1e961f846d1e962d8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000064531e1da97c2af6108a03d9c49a2e47b5bb71f100000000000000000000000064531e1da97c2af6108a03d9c49a2e47b5bb71f1000000000000000000000000000000000000000000000000000274797fa0c06d000000000000000000000000000000000000000000000000001c4a56c7f6b6d4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd410000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b277846d1e961f846d1e962d8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000059780ded40a5c540a536644dae651c54504dcb4400000000000000000000000059780ded40a5c540a536644dae651c54504dcb4400000000000000000000000000000000000000000000000000028b423ed3116f000000000000000000000000000000000000000000000000001b943596512384000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd440000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b278846d1e961f846d1e962d8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000524a06bada4d98aa7c96275b7d7130d3180b0ff8000000000000000000000000524a06bada4d98aa7c96275b7d7130d3180b0ff80000000000000000000000000000000000000000000000000002b55a3ca2b094000000000000000000000000000000000000000000000000001ab7f60b5414c4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd430000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b279846d1e961f846d1e962d8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000a3fa534de502a31714353d91f26e1e77f6084323000000000000000000000000a3fa534de502a31714353d91f26e1e77f6084323000000000000000000000000000000000000000000000000000139d8db69f480000000000000000000000000000000000000000000000000001f438daa060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd450000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b27a846d1e961f846d1e962d8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000001d35a60b0b6815cac022f0ff5bffe4371a23ec460000000000000000000000001d35a60b0b6815cac022f0ff5bffe4371a23ec46000000000000000000000000000000000000000000000000000139d8db69f48000000000000000000000000000000000000000000000000000354a6ba7a18000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd460000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b27b846d1e961f846d1e962d8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000093f09eb91afcd7d4de05dbdfadde30d53d20580200000000000000000000000093f09eb91afcd7d4de05dbdfadde30d53d205802000000000000000000000000000000000000000000000000000241b57c404012000000000000000000000000000000000000000000000000001c4b5e4c48b40a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd470000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b27c846d1e961f846d1e962d8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000dd47d3db30809a107c1dd59417a61230458d278e000000000000000000000000dd47d3db30809a107c1dd59417a61230458d278e000000000000000000000000000000000000000000000000000139d8db69f480000000000000000000000000000000000000000000000000006a94d74f430000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd490000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b27d846d1e961f846d1e962d8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000863ca2c8116aaa8d8674a0b3491fb9fbb99ee706000000000000000000000000863ca2c8116aaa8d8674a0b3491fb9fbb99ee706000000000000000000000000000000000000000000000000000139d8db69f480000000000000000000000000000000000000000000000000000aa87bee538000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd4b0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b27e846d1e961f846d1e962d8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000012cb6f5eb0d90f8978deb060c46539e8b872550900000000000000000000000012cb6f5eb0d90f8978deb060c46539e8b87255090000000000000000000000000000000000000000000000000001398c01372ae0000000000000000000000000000000000000000000000000002aa1efb94e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd4c0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b27f846d1e961f846d1e962d8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000245722555190dc99ee569cdd1c5a67e05ce62dfa000000000000000000000000245722555190dc99ee569cdd1c5a67e05ce62dfa0000000000000000000000000000000000000000000000000002c38faa4ee925000000000000000000000000000000000000000000000000001c0e839c49bb12000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd480000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b280846d1e961f846d1e962d8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000da9a19e7831402175051adc494f67146e5827e43000000000000000000000000da9a19e7831402175051adc494f67146e5827e4300000000000000000000000000000000000000000000000000013498d5b048e0000000000000000000000000000000000000000000000000001ff973cafa8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd4a0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b281846d1e961f846d1e962d8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000099877f4429978df7552fde876e0eec345497444600000000000000000000000099877f4429978df7552fde876e0eec345497444600000000000000000000000000000000000000000000000000013498d5b048e0000000000000000000000000000000000000000000000000000aa87bee538000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd4d0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f482e70882402285012a05f200852e90edd000825208942aafe94148739a6e868e32cf8b1ca9fbc81e2d0987121e6c485ac03780c0",
+ "0x02f482e708823ed485012a05f200852e90edd000825208941981f7efb48591ee64bdaf0d61fe042e39fc12e3872386f26fc1001c80c0",
+ "0x02f482e70882402385012a05f200852e90edd000825208940d8c44854055f4becdfee63d2f6286548ace0c34871550f7dca7028780c0",
+ "0x02f9029782e70880846ba55515846ba55515830282449480e38291e06339d10aab483c65695d004dbd5c69871d21db47288000b902642cc4081e00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000010d92ea475e7ce7010000000000000000000000000000000000000000000000000000000064bc7def0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001d21db472880000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000046ec4bb184528c3aee6f1419e11b28a97f33d4830000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f000000000000000000000000a550c99253c5ee0136ac37eb610a28dfe2cc093a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9019782e7080a84623ed54584623ed54f8307a12094da4c3eb39707ad82ea7a31afd42bdf850fed8f418709a12f75a9e800b901649caf2b9700000000000000000000000000000000000000000000000000000000000000e00000000000000000000000009dd711b0cb4430f429231e5cb9940dbd1952a36f0000000000000000000000000000000000000000000000000000000001e13380000000000000000000000000000000000000000000000000000000000000012000000000000000000000000007039fb83798d979bc4697ab4e3e1bb715bf95700000000000000000000000009dd711b0cb4430f429231e5cb9940dbd1952a36f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005736869667400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000056c696e6561000000000000000000000000000000000000000000000000000000c0",
+ "0x02f901b782e7081184623ed54584623ed54f8321710694a02573c4ad15c16b48f10842aac9c9ea405b65a38701d704a97b9400b901845190563600000000000000000000000069421eeb27ca0941bf46e071098172a56b35df97000000000000000000000000000000000000000000000000000000000000006600000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000003d2a1800000000000000000000000069421eeb27ca0941bf46e071098172a56b35df9700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000001469421eeb27ca0941bf46e071098172a56b35df97000000000000000000000000000000000000000000000000000000000000000000000000000000000000002200010000000000000000000000000000000000000000000000000000000000030d40000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f86e82e7080484623ed54584623ed54f82b3cc94e5d7c2a44ffddf6b295a15c148167daaaf5cf34f80b844095ea7b3000000000000000000000000438670d41d5118003b2f42cc0466fbadd760dbf4000000000000000000000000000000000000000000000000008e1bc9bf040000c0",
+ "0x02f86e82e7083184623ed54584623ed54f82721794265b25e22bcd7f10a5bd6e6410f10537cc7567e880b844095ea7b3000000000000000000000000438670d41d5118003b2f42cc0466fbadd760dbf400000000000000000000000000000000000000000000006c6b935b8bbd400000c0",
+ "0x02f901b682e7080584623ed54584623ed54f8303b8da949e66eba102b77fc75cd87b5e60141b85573bc8e88695a0387c24e7b9018451905636000000000000000000000000f4c21df1cc8dfca3b064e9b3c9a0f434c3b9039f00000000000000000000000000000000000000000000000000000000000000b800000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000017d81d6000000000000000000000000f4c21df1cc8dfca3b064e9b3c9a0f434c3b9039f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000014f4c21df1cc8dfca3b064e9b3c9a0f434c3b9039f000000000000000000000000000000000000000000000000000000000000000000000000000000000000002200010000000000000000000000000000000000000000000000000000000000055730000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013082e7080b84623ed54584623ed54f83019a6894508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000ee99f3fb8744dd05cbbd87104b7f088d194992ae000000000000000000000000ee99f3fb8744dd05cbbd87104b7f088d194992ae00000000000000000000000000000000000000000000000000016b3f8f9ae760000000000000000000000000000000000000000000000000016345785d8a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd230000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f86e82e7084484623ed54584623ed54f82b507947d43aabc515c356145049227cee54b608342c0ad80b844095ea7b3000000000000000000000000c66149996d0263c0b42d3bc05e50db88658106ce0000000000000000000000000000000000000000000000020ddba8ea3d826848c0",
+ "0x02f8b682e7081184623ed54584623ed54f829a3e94d9d74a29307cc6fc8bf424ee4217f1a587fbc8dc881af0e2108677632cb88429723511000000000000000000000000e4edb277e41dc89ab076a1f049f4a3efa700bce8000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000200325a9ead0f563f2ccf8c7afcf1f277a42d7bd16c676881b19cc96d467244f7ac0",
+ "0x02f9027082e7080a84623ec1c084623ec1c08302d77e94272e156df8da513c69cb41cc7a99185d53f926bb80b90244ac9650d800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000104a8c9ed670000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f000000000000000000000000000000000000000000000000000000000000012c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064bc539f000000000000000000000000000000000000000000000000821ab0d4414980000000000000000000000000000000000000000000000000000011925a4182e4820000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044bac37ef70000000000000000000000000000000000000000000000000011925a4182e482000000000000000000000000be8c5b0bdfb7b19c08fbad3c085b05da15cb721c00000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013782e70880845f34f8e7845f34f8ee830390f994272e156df8da513c69cb41cc7a99185d53f926bb878e1bc9bf040000b90104a8c9ed67000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f00000000000000000000000066627f389ae46d881773b7131139b2411980e09e000000000000000000000000000000000000000000000000000000000000012c0000000000000000000000001e7bb53c1eefa4513b69fcbfa900b6bce051a77b0000000000000000000000000000000000000000000000000000000064bc539f000000000000000000000000000000000000000000000000008e1bc9bf040000000000000000000000000000000000000000000000000000000000000464229e0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9047782e70820845d5a946d845d5a947783032773941a7b46c660603ebb5fbe3ae51e80ad21df00bdd1870d801472258000b90444a71c9b7f00000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000001b5722833b955e27a35a12eeab4886b650ad810b39e0de5a25fc177912a8bf476e2a99c9092df5e2f398069970b339375fd1aba8d53eb271e4f1fc1f749d7e5eb70000000000000000000000000000000000000000000000000000000064bc4de3000000000000000000000000b1ebe120c810ab46afcbcbdad7102096fb12634300000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b1ebe120c810ab46afcbcbdad7102096fb126343000000000000000000000000000000000000000000000000000bd011e3e0d0000000000000000000000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064c57e07000000000000000000000000e49cf1bbb229562aea4045133dfd244676b3acb8000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000b62c414abf83c0107db84f8de1c88631c05a8d7b0000000000000000000000000000000000000000000000000000000000000f1000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000955af4de9ca03f84c9462457d075acabf1a8afc80000000000000000000000000000000000000000000000000001599ba503c0000000000000000000000000000000000000000000000000000000000000000041dfb16404843d231528aaba3eaa2193cfc1ce0b72893909d63a87e98524f3b08c4b61dce555815a6366ef6e5c18f6c9832f9d13cc49d81e180429ac8babb9009d1c00000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f84d82e7080b845d4cdd5e845d4cdd5e828caf94e5d7c2a44ffddf6b295a15c148167daaaf5cf34f80a42e1a7d4d0000000000000000000000000000000000000000000000000006b54a2a2c0800c0"
+ ],
+ "l2ToL1MsgHashes": [],
+ "timestamp": 1690062623,
+ "rootHash": "0x0898f77ca827e6a3f6edc35c2d8a7ee7243739441dde6ce6db590b12f660601d",
+ "fromAddresses": "0x46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81be4edb277e41dc89ab076a1f049f4a3efa700bce880c67432656d59144ceff962e8faf8926599bcf8e4edb277e41dc89ab076a1f049f4a3efa700bce8a550c99253c5ee0136ac37eb610a28dfe2cc093a9dd711b0cb4430f429231e5cb9940dbd1952a36f69421eeb27ca0941bf46e071098172a56b35df972d584a6d566ea2cee64ea18dddbcddacbb7f681f8a0df28f173103e20de05093aa6e27829715c9dbf4c21df1cc8dfca3b064e9b3c9a0f434c3b9039fee99f3fb8744dd05cbbd87104b7f088d194992ae939a7ef73572e9218c7085aca6d894bc237f15e3aba9e8b0150c6e612d9d49ff8d0ddf9aa4160ee9be8c5b0bdfb7b19c08fbad3c085b05da15cb721c1e7bb53c1eefa4513b69fcbfa900b6bce051a77b08a9b4221a84bb39faa7d6fe0f7664efec9511acc5250939ab0e1aaf6680d54a2b2154e59a43871e",
+ "batchReceptionIndices": []
+ }
+ ],
+ "parentStateRootHash": "0x0db5a89c27cebc50b75b532636687ca2662de28a5341c5b3d4bcb2b4f4e0b9e4",
+ "proverVersion": "local",
+ "firstBlockNumber": 33118,
+ "DebugData": {
+ "blocks": [
+ {
+ "txHashes": [
+ "0x33b8ad2d347b76c3c78e5ecfe521c283ccccccd72c5195880dae9d00f5eab980",
+ "0x15d34713dbd2b309937e1f53e88c77ad50c6f68c96ecac2116b8f8b854381239",
+ "0x31347092ee78bd11582f48825b094e668933677bc1cc606cc317a0223741f699",
+ "0x9c68df3eb4bf8ed1d92d84ae53567574d591c8ddce51d8b34aaba6b2ccd16888",
+ "0xd82b87e4c4ee4d3b108c44d05b7b46513515a66a805b12d2f413f1631e5d8ce1",
+ "0xce4ec8617cdd1fba8463bb76f3767e155af6cc63db1557a697c20065693de49a",
+ "0x06e6ef03f7e67dcc60ae4160966bec0b5a058c92e918b44c62888ae0fc2d27d3",
+ "0x34611763b2a2f865b5df2264cdc3c049aae13e87ef13d77e21f4316fdd2c5c2c",
+ "0x7eed17b099e00c62c4d432f273ac2d22339040f94dcb9241968819ed8eec33d5",
+ "0xb7163f67cd7d2172418f5deb86a62c067f3b7bccc36d3ebc636d7982847f19ce",
+ "0xce223cd4c2cf0acc26af1041bcdbac3823fca09e09db5cd43581ea14c52ada2d",
+ "0x44d4ecff38101357ee1b74d088d0a9e3ce2852b737a108c848e121dd17973dc8",
+ "0xd83b699282e9e087e95f8554101c16da3ed1135d89a75374232b31867745a41c",
+ "0x5eab0f03880003241b85e2d68f3d2ba8dfb63cf67bff90b6a0c3e2fc30ba34b7",
+ "0xfa0abaf3bb7ce0afd7b0fd21ab78eb79372dfce8faa1d68f8f8dfd6ec83f8c66",
+ "0xafc599712a9a10f6311baabc78c270946698ee80e2598b8ad48e2fd6ae8aeb49",
+ "0xd310932c47ff9048b28e2d5082ce413504bf53d23425a15f278822b00720c2a8",
+ "0x5eab0f03880003241b85e2d68f3d2ba8dfb63cf67bff90b6a0c3e2fc30ba34b7",
+ "0x020f58d8a04fb1b64567234d18ee4e33e847b2ad6af1d486fc8c912afa82764b",
+ "0xe9135db60ba9cb6b1b3f81de31e8530c63757065d9e562033a90e43182c9c0d4",
+ "0xf9a34e4dd66ae14a2dd4c03c52cef0933e865f5dd3b5b311069afebb4914fc11"
+ ],
+ "hashOfTxHashes": "0x34a0bf05552d21800f3a4346c9fe2071767daa22f0fac5ecba21f0e356ec87aa",
+ "hashOfLogHashes": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
+ "hashOfPositions": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
+ "hashOfFromAddresses": "0x0387605e3468cc9ceecd4d5a9c7282899879523dd6d98418fd6a8aa787fdd4cc",
+ "HashForBlock": "0xbb50f9fb374ec77f1060aeea1fbddf182feee9c2c17664e3ae5dd9b13fdfb38d"
+ },
+ {
+ "txHashes": [
+ "0xe1e39f8c4731df31d1e9acc2178dcf7c410d839a708d42b049dc09870c144b8b",
+ "0xd22c7b46589c2c84168199b2522322983d6841dddb95a2db7b73eebb146d0989",
+ "0x251ff80c257ebc858547507fb7393efdb53be26bf6ff5bff60f301bedf5b94d2",
+ "0xabbd49c232d04197d5ec243326a11e5960da70d8fa7bdacb96b80c79035e59c6",
+ "0xb37b55c44982c87b90fc8c6894598697838f300f254a521efe453141e5c73ada",
+ "0xf8e6cb20fe34af5a9139c815493b1cf368ddb6f08cbe7ac87446e59beb45be2f",
+ "0xa252d9a0d4d5cab3f9fac8c3b2a01493aa217e20485b03a7c4e8434752210f08",
+ "0x2a0dfee0cd5a49395ed751442e1a48df0a98b144da058f631cca548860980da7",
+ "0xb27047284f817bfba3aa35c83a566b285f918b08da193cba0f57230ae653124f",
+ "0xb01d7705691db2c1fa48e2790704542a50b6fce60469626ae2ed21193fc5d9a5",
+ "0x6515e946304e6907a1c85b02938f9f5f896a7966b2479350ba3db075293fcf2c",
+ "0x1860df19170373188821b9e11815d7efb3150d291e6171d0c37fbaf5604e0ed7",
+ "0x2f168be2953e5b030453a8dc00624425a5f5e05fd8036182b2fa584136e47f18",
+ "0x781c200c88b39cf217b8484955e220b950fdd1b49752324ed6ab2952d5085d01",
+ "0x588ce20ef4021ff9242e1e9ff98457a668e3f52fd32a4f96df64cf7352b4e6ad",
+ "0xb85a6b9fce54bd60b07aa31c2edb5aab2e835d34b53f649ec4a650b056981eee",
+ "0x6d10ddd6d9d92e06c1254d7262ba5e34c817c0cacf7d36238502e0d3a918b33b",
+ "0x0ea5515f39e5fd39dc5bf490794f5e23270f7b020727972f3d24ec5e2e3c8e03",
+ "0x9a9fd46d45e2acd5b7f0c19194ddb1262a4de9474b0f662496339a99ad867e32",
+ "0xde8039adb7e0be4f80438e1b3c0a8a6ded538b602e90998083b98d8a04d434f2",
+ "0x1c459d9b02928bbef00cfdc579d4f69cc4152b0f12a49f92873ee579c5b3cca5",
+ "0x3e57e9ac19b84df49d9bcf1c90ac40f4607e63445f5d3e728d76d9b6e749e844",
+ "0x74632667d6f68741930f8f9c40f8a0762865fb9388e03ca70377aff3225e7462",
+ "0x033505da513d29c7f3f8a49ad7ca1e6215be9bf6ec0db3b1e85bce79a41634c7",
+ "0xa3f969c864f09c6d66a4208aa5773ebc018ee175153ec0e95a7d195214f443ec",
+ "0x47a10f787f700cde6dbb742f82b38b60595522a5b37a3e71142a464214d12005",
+ "0xb288d2887509b067194d38e298401648b70f7c6e073aec5035d946f0cc96eee6",
+ "0x55ce7f07dfc65be1997f829cc48c17fd648dda59a20f15b0838973c4acc2b6ec",
+ "0x9638728c2348797024b771a7f11a7f9ef24ac0ef47e0f6c07748b5d4087beca1",
+ "0x96695bab277b9a8df0e26c73dadaa2489f7f32d2faff836c39fd1cada7f53e45",
+ "0x30c3fa2924cea3a4b6971e54e7a9e063d03bfc007c381e98653c6058673fbe64",
+ "0x296b77172fe6ca7c93f3a27cad3469fe328f38a6b7d62bff8eb113c562811648",
+ "0xf4fb520848b590bfe47bcf93e9cbf61f77a3d9a2a46ce63711aef77e1252e845",
+ "0xe0c716e1abe1ced5af4a5d16068bfeefe71b6b4b0dafeb52dca0e4d62437c6c2",
+ "0x5504974a707746524142e4d87a85158b6e0c1669f8f7ca8c79a7b0c4ab736e5c",
+ "0xcceafae6719af922d536578ed8d9d80b6f9a034dc6d7220543edcc3aeb50b112",
+ "0xab5ffedfa4d3aebc9fd5b5711fc1333df8bda6e6c83d8795479006ca669913b9",
+ "0xf3d3fcdaf453a7ce863571db913b9e49e43f23f8bc21f9edc0e7ab08607c5b27",
+ "0x697fae72571c7445c528179adfb08739b50654713bcc21093ad0ed0bc2343415",
+ "0x6e26e4d0ae4d713d0d68b7da5cd53ee352f3aa020887014225a0991a2a047259",
+ "0x4a9b986c3b05b410aa1f304c914b0c89eecc71a8e23c0d235a81b3c57e76112c",
+ "0xd8b85bd548e76cbfb53679485bf4af4bdadea983abdb7c5e3958e945144f53ff",
+ "0xcb4d071107666f474f15da1571a8cba5a15d15b8fd6b4a1b7f1b0ba2f8e2a3f6",
+ "0xdd3d6ea98fdd96698f5f13c59e5cdb51f97d02991735ff7b5bc880af4834cb00",
+ "0x2259ed1a2d2ed8462338da523a4dbd29c38d1662de859ca583337663700834e9",
+ "0x6bfcea9251674921d5bf38e17f4ffd92a0a89d1b54910bee0e9e89ff53f49efd"
+ ],
+ "hashOfTxHashes": "0x23d15228532fbb55efd61aca03c88cf041fe29a5fa7bcf2bffec9384f0e1bfed",
+ "hashOfLogHashes": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
+ "hashOfPositions": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
+ "hashOfFromAddresses": "0xf1c8491fc9e3379cca6f1b00229d29f288eb5cf50bf41e2de97f7dbba03560b1",
+ "HashForBlock": "0x62ccbbbb86ef488aeeefa412012b550d385ac709ca4edd5549dcdd1d02c49e70"
+ },
+ {
+ "txHashes": [
+ "0x79c5454b996125abe3a3f1b2308e12bc1a05fbb18ccbd961d1e9ed5a60e99a3c",
+ "0xb48f595c9d8b0b3b430a3e18ce714b338d24e151444c802af42c160e83e1c023",
+ "0x1d33b875399ea2b977069a7a3af5af502816bfa57370cfde72b40b797e2cdf9e",
+ "0xf91e12a540222c88104dd71c2cb312651d5c6dffaf4a397b9b823ce006dc35f6",
+ "0xe22e54f211a9dc27fa8e306b760b41ee2430c7c5a6844b034275cd64d7f682cd",
+ "0xdecf2c8e07903f8fac04580c68b202b99b24eb56d6c9c34204718055169f35e1",
+ "0xc65db255285d080713ff417da3054e95df469126b79c0b4b1dd394ac17441683",
+ "0x066f6cbd57a6747706d1611bc48116ff088aa12174d9416a6ff8ff2c4afe33ab",
+ "0xbfc927e159210db0b7ee968257c58b94f55a203b9ff8f9cebf79e38d2b50de86",
+ "0xad05ed5e0b6f984e3ba8a022d0bbcd0787c0a4cda53d18d0de158c4bfe43972b",
+ "0xcfd19e2cfeebd293c1e087e61be6f9b632fbeb72e01b75af7827dbe8b041b9e8",
+ "0x56f4dfd08b403881832831981bbe7fcfbcd0e8da08994793457617793e63d597",
+ "0x8e67aea74c01a3ea7d16a6b9b47efb220e6816ba9e6029944925fba4be554306",
+ "0xc1809842c56bb4d49ef49c35c5c2016a3979f2f467f3eece542f814a1b308474",
+ "0x4309dbc805acd30e711c932cdffed396b097f18edb2a27310851e275ced82711",
+ "0xc2518d2b1ab1d703158e013adf992efc9696f5bc3b807cf3abed054a30529652",
+ "0x0c1bb65bdc1c24fa132c37504059fce12e3edfd3b9c5053797756ee25f07628d",
+ "0x0ecea1436ce18b7c66c6d9e0690506fb974393608267ddc64759d80365082843",
+ "0xf3d59eaeaf5f227e11c4e676b7789c0ad33940716e091bc5278075bd69019327",
+ "0x88e39cd7ba151a9f8f8dc5c3eaae99377f444b34d9be70404b19149efa6dfee9",
+ "0xa6768e6b696584e34764d4d853b5506ca3e21f6030ad576b5d40264382a7c634",
+ "0x604a18caa65f2241536c5f28cb8c5d2e1f2d8c686f9788137c4f24576a10cb52",
+ "0x2f5fea3ed9bbb33919946e2ea03719dcc12a1ff7267a84a2ec432f08b8a1723d",
+ "0xd209e9dd0197a7c10cffbb502b7f4af0714a4b50686d306a270f61e4855f6ebf",
+ "0x158f1510fe03b404484a2b8150ab0730ef8825854cc80977d4f8a3ea3fc7b759",
+ "0xef7487da45ff07d1ac218c7f192305b556b0d5b0906e4d940c5c9dadd50e3c8c",
+ "0x372ed95455b0932ce3bf40d6a8a35de722c626c513f3d92f4b774e8c89e119be",
+ "0x04b8ca4b2734f82a5327a905be3828d8689772d4104222b3f7f15eac2e41a868",
+ "0x2c15d3cc010c7cf663e30f34034743082d5dd002e5978c1f4781c4ff74cad8bd",
+ "0xde23d5166a4751afd2f80792929b9a699d9583d1c3ab84f327eeb09fbb720779",
+ "0xaa58f51031042cb889097716b22be59129f72504d5b9d6633e97a30ed5598c51",
+ "0xc74f177217d41c5b70ec6a523f277cf91a331a0450aac5b7dbc4ae18332c4652",
+ "0x67c43525a6d0d8b45c9c85f1ff260059338a0ad6ad64d456ca9dbfdecddff06f",
+ "0x49d8163a4f3f04ef21cf35fe7a82384ad926fcea2dcfe2a9f01825cc0fa1be2e",
+ "0xe8986d3c28f95a68a57ab7612a6dc801ce17c615cc9ca3dfce9ab6ab28e647ee",
+ "0x6e24f6034ffe2a99719606173f177f119a848f959976fba77d19c5d20523b196",
+ "0x3dd9defe080c662bece7e824f5af8cc6cebcb5d30c1023ea699f2021d2997c10",
+ "0x2f4b77b8a052f4b2fe9233104d6f51bb0c312f524bf315eac8c1544448f13340",
+ "0x7851ce66a67cef5c273839c5c4eaf2a4a1ff58e86c7b9342aa9743e4c462b079",
+ "0x6839a1b38d40df547c006156e5e82f03be4f3e3bfd9fa744cee1c3ec6c6c0b15",
+ "0x271c0dab45c8118a83fb964403263ca7fc4ab44ac86155732b6f66fa08233e0e",
+ "0xcfe46e3f690749455af944d0e9dd3195056898852da82c1298ba63bdf907a37e",
+ "0xb771169740aa9d7d2ed14d10d6b511c960135c877a0eb47cb41d557828268875",
+ "0xc120090ac715d0c88ed0652ca3c81727ebeed0a98b546c71db314decadd0a26d",
+ "0xd452e7c1936098aa268625f147892d1fb3e8e06ba4b9ab2b91a9950644f966c6",
+ "0x27e29d73fa87e9a21a2c2270031edadfeceb2f77392e2dc86023f814775a872c",
+ "0xd499a99d846b7012f01b53a47b8fd1701d89585c59179ea57a19f1de36b81e20",
+ "0x695bea22f78cc9fb72a191911ca33bf1183c7f98e9e34cb2a7e905cb75407c0f",
+ "0x265fabb3192edf158edf7074d30645e4dd1875c77c5d086089feb20622f5b88b",
+ "0x96e46b4eff69253f37399723307a09b3c9de0979db5c48d63e80bc418a57b5bd",
+ "0x48e0a7cfbf088dd37b003dab637c7431ac817220a4d0f9228d6371571e8281ca"
+ ],
+ "hashOfTxHashes": "0xd6407d1b3333a2efc5d37194c55eea9cb8e8fad53fbfd7d009ea3d5019c75128",
+ "hashOfLogHashes": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
+ "hashOfPositions": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
+ "hashOfFromAddresses": "0xf9f76121ce60580d7997cec266645cb12da15caf882ff0075829c2b7243ece91",
+ "HashForBlock": "0xf0e25e3c06a11aa02737a878098dbd5b246c46f29b4f45244c14024e8b22931f"
+ }
+ ],
+ "hashForAllBlocks": "0x0b7ef7c824a8ea7f49231802e8bfcb992f756fdd48e086c8f992f63caf1cedca",
+ "hashOfRootHashes": "0x7dcc64e8c3e428328d3f95778f7340d5fe39015147dc430bdbfb0901892edb6f",
+ "timestampHashes": "0x4662757f920ee71ddf31fa1a88c04f98fb63ef67a3032bc34c12a3ddacba363e",
+ "finalHash": "0x3041a1027bacc392df45fe2e0a35293974440621ecf1efca11e5359819869829"
+ }
+}
\ No newline at end of file
diff --git a/contracts/test/testData/FullLarge/rollup-1.json b/contracts/test/testData/FullLarge/rollup-1.json
new file mode 100644
index 000000000..b122fad87
--- /dev/null
+++ b/contracts/test/testData/FullLarge/rollup-1.json
@@ -0,0 +1,308 @@
+{
+ "proof": "0x212c3964774eb071fab45629ea97c0986a43cf96703d1a879fcc4c0c9d3b1a432e88b8c5783271091def99f2ddcf7f27d726ad66d16d6dbc0fa6410390a58feb2fa89e648ec72184dce7c1af6ec4f238125744801885a5d6e1f9e62e43c39abf27160f114a08714c4a8afaabe12326f8fd04efd6edd17eecbd8db7d39249d308292906a0f8f80c2850512674a0fdc8e0a95b91e0b71ab2ae7e6d59ddf2ad9f870a5e2592135fb09d848546b34d1e3321767428e8aac5afe660824c09ccfdbf5c0fa846a14d4527a6b9de863adf6212a00a0387100a36c776265920b1f8ffca8f0d95ebd0b851258f780ddc6729d50656710a7196ccfee1d0098b07de802b4d0211019f8371ae38be4b4a7a33f3eb4c203ec69e39a86da22d5443f091ff695574030bd4fabf668809f6dd1060bf289156b5b435a26915c1fa24bfa2bcabbb006017a7f20150e98d9ffaa77cd6cc03b533ac3928c96daf8d6c397bcb6519085a7816087cd7d84f7d8892f6b16842f18ca3bf34b07f05ea55b9f7cbdd20497c80c7219e4a2651fa28f103ab5c256ca0328f7c2b83382b62f7b679f64b7923b37e672e9e4ce6b01f51c225527613c59c1f42ad6cad4d1d27b0f25532ec2026350a6f07478ce7ad597e7ba666b355bb6bc79bcb7aae209449ca1f2dea9c18d7eaacb80dd19278336a6d828287ab347362c0ee28f1f9d38398b0bec380cd8d877d9bd00b61150fffcf3c7cc1a76d68b31d235cb12463c46b700fc4c18084eb0a9892102e48720daedb426a6699da6c56dc4fbf09cfaaedd143c91441e4aa8865b57e742ea607dc87aac7f34aa28bb868581cf5c4b29e054b26856b52bdc300ddd46ea106dbaa1b3e2dfa266a8670e2f695b95bb2fb224be1663d14356cc0188e8be1e42054fc15725d03c62d5b7f7dfa546772d25edda2bee1201a03e9f6b74ab10cb2123adec3f80e2aa792d9692a32ee16169b619568f88b902d1d75bf3af63d053717d1843d32b4b15189d0af7a99956c0993fc7a703e554b84a17341ba5da899162b5a7c75c4973aa7d3afd1ebcc97e9f13b6f57145cbc679bacbb98b2703035f11da13a576b9736081b11365bad8ca0760c2a31004faf8f3816752ffca10270ff1928f4ff06a35cc77aa10bbfa0717346e1873757f42b6405c41f41a0fb20f60228d29bf6c9342745014e360e4baabd2b3427f49742f8cce63e837061827df8ca2cd8aa1938903468c68b6d888b93ae7a2022fe4fc83e8668538bc480e1c356e304bca6c75d6611c7cb6752ce914ff709be06f356463d132fe2b1f9111b2f7c80",
+ "proverMode": "full-large",
+ "verifierIndex": 2,
+ "blocksData": [
+ {
+ "rlpEncodedTransactions": [
+ "0x02f9091382e70882054484962fc6ee85174876e800832625a094508ca82df566dcd1b0de8296e70a96332cd644ec80b908e4f4b476e100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000045161d5e6ce5e92df9502203fd8e60b26cb70894d99155576d7776057cf189577b3f762c86d9b7dcb0ccf3be280ac541f2d0514fcea664dadd7456cf50eaf2d2f10013acd8b6ab926aebce6479e1e69cba954d9a12280333a01764665a5753f2e465feaf9735a1a209af27b3961e604f09a79ed9ddc8cba4fd3f32446a2018bdbc0f876fee16158abb8ee62791d73979460eae03f4463c687662eb4ed55366f286fef0cc22ced3e884f6ada00b47ab4a643bbac88289e65a41ee741307eda80be8e8964030c3a8d5d5c81f99999a0b6d68a7b3af3a9a077c1d1cb7a61f0d1199089baf45c5dd81d359e4744b02f811ff6826d0db21c23686440c3c4019023691a394a7655f567ed5441ff236f1bd06db8fe26c9ceada60fd7f4b2180ad0db7833092bf2c52a1d0b4fe82a78bded7ee559ffb37f74de252e169ef907faa79cc84e888b947aad4534065024a5c3932e1ffa287fda0890fae04b45c2913c31fd992322973f916e0feaeee2180645f59b3336a07d7fdb2db52fdde27ccc544e2b030efa3db5e99efaab00e74314333b92bf5c358a61fd7e4ff920e8cf903866615adeaf2df544df269404bba4d4f94e87fc0f33d980511b4f3677eb337784bfb50209ae5f9fc6642c89a99698aec9767a936745e5538815221121ff20e1b6c89deeb292ea4eb17dbdd045924d3afcd4c418cf4f6a0a10c7231bbf852f2f4e18393f13cef6772e7e57ba3c74a3df64ae759e47be96b1185898570ce963d9c67acc30cfcb5ee5930478f657324e1427c76e49afd25111e0c9eec6842334ed75d0c5ba1c465342c6e75e0c5439f2fc0ea9761fe2b6890247f549ca4167425a953e642c31c9159b2d892edbee614e439766a7ddd8a66cd25bf9a45d2d1f580cf91c77386c1df74a4d98d38af559dc53177503e9157c6df8c8889d7f47bcee62749efb5b0614cce452e428e4780faad5ab385f77ecb89686c1c026927a9607bcd368c1212b0ee1dae5b27bd4796eb07b7cbb65accda47b8fc2336d53611ba5a880e3a84b41a65a9ff6e91c0f8ee8e00b506e81a8eacefc803d9474edbe0380898f324d49ea68fa2c7b2f5d52ee5e47308a0b2788937bb1195f5603845b07eeaad81b6d65f31028e614b18f73872399f6b643c8e33eb88eabdce099ff3eacd42193ea732a22826c6e79c13acb66deed9ca11ce95ffcd356a25e4f3e4b60f7fa36cd40d7171589fe695da70d6c976a4d79a84137713bd63eed6e5622f351faee3169cbb59c327d02471a720e7648c4c47a6592cc5d7c148fefbfeafd91d8ad350e14c691680e8fd47baf17f82b0aab2c3dbd23c76434c7b4e07c1e1c21a98886e2c6e866b8a60af2c75df6e29e0b40000070c6710f29e1b4e0c2e18cb2cf68344c8e259ee85408585f12b4ab7316f36b3c31fd1097faa1a036b6f1a74015420b3eca68daa03002ab06d810efce532fcd0996960288a1be9a2a43ac828b3ab4538853860a9bfebd8b7c0f65cdc54138137aed477733cc9726b748679d13f516150ccf39410795f32517bfbec0f3ed4dcfe2ef70029e35cacba809bbbc2a6c82543b19c1c1bff7fab52327ac00465b35015d9baaaec5a5aba682f9bef177e216e9e77319aadf64d7790878c01cef76b23eba1ed55f16ecfea6decde45118d2efbe4c22fb630d1c27742f4723148a934c1a97454fbf3fd603ef5a3f7d38ce4ae87bad4c25523afd669a112750bfaebf85f2e465cfbfb3bf8bad1b558ea91c556145eb87ec02f984d8c2f44369c14bf265536a5f32d36711add8c3ae841ce268b0c52533c64e3d841c9d4b63658f8e0ad32bb512aa7fb77f3baf72ff71bfe214676fa5650eae2971d0ce43b49abf645430874e5fc9dccf668217040fdf3cad9ab0140915c65fd5e9816743a249984e91746c5f0670d7fad7d642158142d570a7ec9e761f37bbc7ea7245befdeda160dc55ef1e2512a16ef5a3668ad785acf4fa836902aeb07ae03ceb24267beda32f3cfb7f5a9929434a3a9f6ddf7e4cfaecf5b071578c047d52a857dd5b30077d6a874892558f8798ee1c335aec8c8e47918ebec1f2a6c1cedc7b4d8185fc041defbef038d3b379ad7e84a047c132a49d0a28e86adf4d11d50da3a8875e89291558805f75e43c02645f08c8444184b87ab93d644b0051e7a6a756a059a97e91a82349d8907a33c1c90daa6e66deefb448b2cde8fe121bfc8b582c556527a7bfb11fde9b4bc645ad35369d630fe3593e020630231687d04cc5c81317eee0976e63f63644f20dd993203ec0ebf3be9af1b3a2d9ee3a779202d69d3d27b7aed886479604731caea8e3c8d878f030acb22a92fde9856ca342222759a9ad734e4a280c23573a003230f1c515e66c531beb518c2c4786d64c46c49854c2fd17722e1a82c08b3a35bc59f7771f29c4bf4e6a18ddebb66beac48ad7bda9122be176ded174b963bb4f3b8b13efdb9bef4608b9f3fdec2222b683dfa6bc41f247f76b2644dfe71e95f5b0b46aee8350e95a3afb46262a20d0e99e9216a317a571a319018d134ef83c9e335af1e46d0eae1377320c8036394d7218341eb4c775a97c2ca8817d1d0febd1e48604e33b395a13a9a36f1151fab3b1470bcfd924e6442ddc8cb7284bb159abef1d2ce5f23c3d892eb74bd31f02a8754aaf9d07476724c70349a8fd5d8d370579461052a9aae0072e79b5fb5a8b5c0f0ee747747554bb1608f7670b9e14b680ce360caa6097ccee08710816b325ac9d18038eab16b16de7a4d727f9dedb9a145a39952e31b264c136bd46d8b45f8683a2af9f27006325ad9e342bafb6ac4756485a79d8cf2d63d72a2284d1118bd195098891bc23831b605b47a9c8665d9bb7b7eb7b5966bc37a56c53f6fcbe28953add2b9fb7d6bf7d9d480563e0cd07f05226c9c8a3c3496cebed0000aced1c27ad9e3494362cd6da3d8686dde841fcc724d830e71160c0359393d089ae3e49f412b567823e5efdb4f43d246e35b2e73620a8b0d6c3f5f45709180c6ebeb48211c422f65a5374b5914b0d3fa1ca03f1960c7a50b6ddba710de8859c132e48e57c57485d937c11c4648fcb2b8a636f3b2a1e27ced0e081c57e8e3f50b5c78481fe743daafd2d15cdfc0",
+ "0x02f582e708823ed185012a05f200852e90edd0008252089452578e1fe11f92618dd178629156de2307b1065d880234e1a85749806180c0",
+ "0x02f86e82e70813846ba55515846ba55515827223947d43aabc515c356145049227cee54b608342c0ad80b844095ea7b300000000000000000000000080e38291e06339d10aab483c65695d004dbd5c690000000000000000000000000000000000000000000000009275143d32c82d5dc0",
+ "0x02f84d82e70808846ba55515846ba55515828cc794e5d7c2a44ffddf6b295a15c148167daaaf5cf34f80a42e1a7d4d00000000000000000000000000000000000000000000000001866c3674ca28e7c0",
+ "0x02f9029682e70801846ba55515846ba55515830282209480e38291e06339d10aab483c65695d004dbd5c69861b48eb57e000b902642cc4081e000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000fcb9d92cf386180000000000000000000000000000000000000000000000000000000064bc7dcb000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001b48eb57e0000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000046ec4bb184528c3aee6f1419e11b28a97f33d4830000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f000000000000000000000000126824925a67f98dca1eb9d92d78d51c99f3a42700000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f901d082e70806846548b1a4846548b1ae8307c07494438670d41d5118003b2f42cc0466fbadd760dbf480b901a4ea5406320000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f000000000000000000000000000000000000000000000000000000000000012cfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed928fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed9dcfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed8ecfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed9a000000000000000000000000000000000000000000000000093a7cafa8b08e3b30000000000000000000000000000000000000000000000000011c37937e0800000000000000000000000000000000000000000000000000046823c1fd558d2f60000000000000000000000000000000000000000000000000007351f791fb3be000000000000000000000000a8a71d5fb2d5fbc8f31799c5cdd8ae67722885a50000000000000000000000000000000000000000000000000000000064bc5393c0",
+ "0x02f86f82e70809846548b1a4846548b1ae8301042f947d43aabc515c356145049227cee54b608342c0ad80b844095ea7b3000000000000000000000000438670d41d5118003b2f42cc0466fbadd760dbf40000000000000000000000000000000000000000000000047049d62f86d62000c0",
+ "0x02f8cf82e7080b846548b1a4846548b1ae8303374994b29caa2cb1feb7f4ccaa9dd9b8ad2022eaca6ec380b8a46170b162000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000413f019ead393d9d0c66745b08e818fe13179bd3da2d6559b2bb9c40aca9618c364ecece8c8fc0d89d0e5897210ae3b5521f49cbb178e00ee568be62d583edceb51b00000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013082e7080e846548b1a4846548b1ae8302cdc594c66149996d0263c0b42d3bc05e50db88658106ce80b9010418cbafe500000000000000000000000000000000000000000000000098a7d9b8314c0000000000000000000000000000000000000000000000000000001420824645e5d100000000000000000000000000000000000000000000000000000000000000a000000000000000000000000078ea6f35af55843f9d1d654f4a076ef435fa34690000000000000000000000000000000000000000000000000000000064bc539f00000000000000000000000000000000000000000000000000000000000000020000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34fc0",
+ "0x02f9013082e7080e846548b1a4846548b1ae8302cdc594c66149996d0263c0b42d3bc05e50db88658106ce80b9010418cbafe5000000000000000000000000000000000000000000000000a688906bd8b000000000000000000000000000000000000000000000000000000015f4b85f94d69f00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000005029056a3581629953feba7cba2446b6057295160000000000000000000000000000000000000000000000000000000064bc53a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34fc0",
+ "0x02f9013082e7080e846548b1a4846548b1ae8302cdc594c66149996d0263c0b42d3bc05e50db88658106ce80b9010418cbafe50000000000000000000000000000000000000000000000009cd1aa2149ea00000000000000000000000000000000000000000000000000000014acf9cb1dc31100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000326b931b50a190704341c4f832ebd7a75f4e86290000000000000000000000000000000000000000000000000000000064bc539f00000000000000000000000000000000000000000000000000000000000000020000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34fc0",
+ "0x02f9013082e7080e846548b1a4846548b1ae8302cdc594c66149996d0263c0b42d3bc05e50db88658106ce80b9010418cbafe50000000000000000000000000000000000000000000000009f98351204fe000000000000000000000000000000000000000000000000000000150a9e61f0246600000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000d7c0fb08b29ea8bbca31b51d6d7bebadf288e2af0000000000000000000000000000000000000000000000000000000064bc539f00000000000000000000000000000000000000000000000000000000000000020000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34fc0",
+ "0x02f9023782e70816846548b1a4846548b1ae83054dfe94438670d41d5118003b2f42cc0466fbadd760dbf4871d2b46f19c59e6b90204ac9650d800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000104c238a3a30000000000000000000000000000000000000000000000000000000000000fcbfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeccf8fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed0e0000000000000000000000000000000000000000000000001f9129260ed919f98000000000000000000000000000000000000000000000000001d2b46f19c59e6000000000000000000000000000000000000000000000001d1781783df86e0cf000000000000000000000000000000000000000000000000001902f4b0f0b9cf0000000000000000000000000000000000000000000000000000000064bc53930000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041faa413300000000000000000000000000000000000000000000000000000000c0",
+ "0x02ed82e70880846548b1a4846548b1ae82b22494cce9d3f392c135dc038b147ca73ec496f7f89d938084183ff085c0",
+ "0x02f86e82e70803846548b1a4846548b1ae82fb2a9466627f389ae46d881773b7131139b2411980e09e80b844095ea7b3000000000000000000000000438670d41d5118003b2f42cc0466fbadd760dbf4000000000000000000000000000000000000000000000000000000000046b7f0c0",
+ "0x02f9013082e7080f846548b1a4846548b1ae8302cdc594c66149996d0263c0b42d3bc05e50db88658106ce80b9010418cbafe50000000000000000000000000000000000000000000000009f98351204fe000000000000000000000000000000000000000000000000000000150a9e61f0246600000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000cc6fc47b6a3a03b41f43fd33c699a1ac32e3b6910000000000000000000000000000000000000000000000000000000064bc53a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34fc0",
+ "0x02f9013082e7080d846548b1a4846548b1ae8302cdc594c66149996d0263c0b42d3bc05e50db88658106ce80b9010418cbafe5000000000000000000000000000000000000000000000000a25ec002c012000000000000000000000000000000000000000000000000000000156842a209508f00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000002183ba8662f9f0e1bebffab11e31ba8cae53bcec0000000000000000000000000000000000000000000000000000000064bc539f00000000000000000000000000000000000000000000000000000000000000020000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34fc0",
+ "0x02ed82e70880846548b1a4846548b1ae82b22494cce9d3f392c135dc038b147ca73ec496f7f89d938084183ff085c0",
+ "0x02f86e82e7080b846548b1a4846548b1ae82b7679466627f389ae46d881773b7131139b2411980e09e80b844095ea7b3000000000000000000000000272e156df8da513c69cb41cc7a99185d53f926bb000000000000000000000000000000000000000000000000000000001ee98853c0",
+ "0x02f86f82e7080b846548b1a4846548b1ae8301042f947d43aabc515c356145049227cee54b608342c0ad80b844095ea7b3000000000000000000000000438670d41d5118003b2f42cc0466fbadd760dbf4ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0",
+ "0x02f082e7080f84623ed54584623ed54f8252089445a318273749d6eb00f5f6ca3bc7cd3de26d642a87b8bdb97852000280c0"
+ ],
+ "l2ToL1MsgHashes": [],
+ "timestamp": 1690062599,
+ "rootHash": "0x290c378f1d39464c72c71773d84030cbf3d354bca03f92caae4139a06876790f",
+ "fromAddresses": "0xc1c6b09d1eb6fca0ff3ca11027e5bc4aedb47f6780c67432656d59144ceff962e8faf8926599bcf86810dba12317406828d64b07d34ad22338644bb3c4f53c1aeb4e8f12b2c427e7a4505cd189be3392126824925a67f98dca1eb9d92d78d51c99f3a427a8a71d5fb2d5fbc8f31799c5cdd8ae67722885a552e085691b4ae78da51187a5e9d8381f08fe2bd392b1a1c7c7542e23a65e528d2d58a891c9aa475978ea6f35af55843f9d1d654f4a076ef435fa34695029056a3581629953feba7cba2446b605729516326b931b50a190704341c4f832ebd7a75f4e8629d7c0fb08b29ea8bbca31b51d6d7bebadf288e2af4c9728684fd37254a82956ad9a8a87bbd938364a7a145a31686980565976eae84024d4ca9c06bda82d584a6d566ea2cee64ea18dddbcddacbb7f681fcc6fc47b6a3a03b41f43fd33c699a1ac32e3b6912183ba8662f9f0e1bebffab11e31ba8cae53bcec359084e2583e835807164c0f864ecff655d1849f4de89c2ae04a31cc2dba3e2da11a38ddc43682b90ff2e6c1d14ce8a94e74d0eb9d6f2536363aeb409232224978f9b83b580ba028bdab92d7976ab41f",
+ "batchReceptionIndices": []
+ },
+ {
+ "rlpEncodedTransactions": [
+ "0x02f9013282e70882b240846d0182b0846d0182be8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000090ae52f244688d687b693d99769e455a38de9b2b00000000000000000000000090ae52f244688d687b693d99769e455a38de9b2b00000000000000000000000000000000000000000000000000013811a83d9bc0000000000000000000000000000000000000000000000000002aa1efb94e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd090000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b241846d0182b0846d0182be8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000052df1b28c2afc2cbb0b320fd787a7844c6e580cd00000000000000000000000052df1b28c2afc2cbb0b320fd787a7844c6e580cd00000000000000000000000000000000000000000000000000013811a83d9bc0000000000000000000000000000000000000000000000000002aa1efb94e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd0a0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b242846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000000bda323c78a069cee9d2265ced3bcfa1332ba7070000000000000000000000000bda323c78a069cee9d2265ced3bcfa1332ba7070000000000000000000000000000000000000000000000000002e50893ba5a59000000000000000000000000000000000000000000000000001b0d7ea876e025000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd0b0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b243846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000b9652a464ee262d5aedc9189fcb59ea04441b21a000000000000000000000000b9652a464ee262d5aedc9189fcb59ea04441b21a00000000000000000000000000000000000000000000000000031b636ca3611e000000000000000000000000000000000000000000000000001a66ab71d3c9ad000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd0c0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b244846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000d1a6dc9b8762fe8c93c5ac103a1e365cc9fb5a06000000000000000000000000d1a6dc9b8762fe8c93c5ac103a1e365cc9fb5a060000000000000000000000000000000000000000000000000003391f90d6498f000000000000000000000000000000000000000000000000001ab250d3446d10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd0e0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b245846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000a9b13af6181ff98c9eeaf9ca46095ee2f916f832000000000000000000000000a9b13af6181ff98c9eeaf9ca46095ee2f916f83200000000000000000000000000000000000000000000000000028c843d42655a000000000000000000000000000000000000000000000000001a62ddacb258dd000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd0d0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b246846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000004acc7828d76abf4f84105f7c826a0d52b00ba38a0000000000000000000000004acc7828d76abf4f84105f7c826a0d52b00ba38a0000000000000000000000000000000000000000000000000002827a4d2c36e8000000000000000000000000000000000000000000000000001b5434999b29e6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd100000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b247846d0182b0846d0182be8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000027bc8621471d8d84475a211fb7556c158958d0d200000000000000000000000027bc8621471d8d84475a211fb7556c158958d0d20000000000000000000000000000000000000000000000000001429fc0610640000000000000000000000000000000000000000000000000001c110215b9c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd110000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b248846d0182b0846d0182be8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000007e4e0c3be87aef0e846162c40d96c7ffbcf1ca130000000000000000000000007e4e0c3be87aef0e846162c40d96c7ffbcf1ca1300000000000000000000000000000000000000000000000000013811a83d9bc000000000000000000000000000000000000000000000000000f8b0a10e470000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd130000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b249846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000ded87809191d1ce529b875d81478f08c4e7e1028000000000000000000000000ded87809191d1ce529b875d81478f08c4e7e10280000000000000000000000000000000000000000000000000001967d1439851800000000000000000000000000000000000000000000000000523f1929090ebc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd120000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b24a846d0182b0846d0182be8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000004f579b878eebf064b1d1f1d8b8032f869ec650190000000000000000000000004f579b878eebf064b1d1f1d8b8032f869ec6501900000000000000000000000000000000000000000000000000013e2428d4b4e00000000000000000000000000000000000000000000000000017cd9d4ffec000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd140000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b24b846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000e512e419213e04728cf87c784620cd1bb0e91fdc000000000000000000000000e512e419213e04728cf87c784620cd1bb0e91fdc00000000000000000000000000000000000000000000000000025ab9cef6d3b6000000000000000000000000000000000000000000000000001c3ef9184c62bb000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd190000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b24c846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000004667317607f05721a60b69e2da3254b58b4fa2150000000000000000000000004667317607f05721a60b69e2da3254b58b4fa21500000000000000000000000000000000000000000000000000027612ae8381aa000000000000000000000000000000000000000000000000001b3ff4d6abb3dc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd170000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b24d846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000a91a200fd2959c0a3bb530eb6e14c5376fadcce3000000000000000000000000a91a200fd2959c0a3bb530eb6e14c5376fadcce300000000000000000000000000000000000000000000000000024479a76f5533000000000000000000000000000000000000000000000000001aedb721196262000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd180000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b24e846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000a9b49485282fe1d16a1e7dffab0378fd2922e27f000000000000000000000000a9b49485282fe1d16a1e7dffab0378fd2922e27f000000000000000000000000000000000000000000000000000233e4626489ee000000000000000000000000000000000000000000000000001b36c2a7ac1d62000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd150000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b24f846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000059ad5ae25a4c2e40ef2fb5df27db491af72d6d4200000000000000000000000059ad5ae25a4c2e40ef2fb5df27db491af72d6d420000000000000000000000000000000000000000000000000002b7ebd3b6c5f3000000000000000000000000000000000000000000000000001c6d36103d6026000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd160000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b250846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000c754a76208d323fabfb5e60b5cd6602673236bb3000000000000000000000000c754a76208d323fabfb5e60b5cd6602673236bb300000000000000000000000000000000000000000000000000022fbb5512f782000000000000000000000000000000000000000000000000001b7679a96d2844000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd1a0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b251846d0182b0846d0182be8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000001ed2d8eebcd1d0319a4e5374bdbed288589f00a10000000000000000000000001ed2d8eebcd1d0319a4e5374bdbed288589f00a10000000000000000000000000000000000000000000000000002691003e5e8d6000000000000000000000000000000000000000000000000001b10359ee7ad8f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd1e0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b252846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000dfa435f8df3036bab2e1ffe4923840178ea54c3e000000000000000000000000dfa435f8df3036bab2e1ffe4923840178ea54c3e0000000000000000000000000000000000000000000000000002a991e6a46a9a000000000000000000000000000000000000000000000000001bff45bf1da75e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd1d0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b253846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000feffbb43619cad27dc96a60618495be1a8882f5b000000000000000000000000feffbb43619cad27dc96a60618495be1a8882f5b00000000000000000000000000000000000000000000000000026a801a888074000000000000000000000000000000000000000000000000001b103c21e6c968000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd1b0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b254846d0182b0846d0182be8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000e6f9e9783211e6d22108bf30d448e27694479d38000000000000000000000000e6f9e9783211e6d22108bf30d448e27694479d3800000000000000000000000000000000000000000000000000016b3f8f9ae760000000000000000000000000000000000000000000000000001cc6e836ae4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd1c0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b255846d0182b0846d0182be8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000dae2f9005c7c2bb2a29bf4eee6ee8c29cc3af7ff000000000000000000000000dae2f9005c7c2bb2a29bf4eee6ee8c29cc3af7ff000000000000000000000000000000000000000000000000000237fe04de55fc000000000000000000000000000000000000000000000000001bf2081e5ca582000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd1f0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b256846d0182b0846d0182be8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000b25cb5c1c5ac5b59971e907ed97e90364b760e30000000000000000000000000b25cb5c1c5ac5b59971e907ed97e90364b760e3000000000000000000000000000000000000000000000000000016b3f8f9ae760000000000000000000000000000000000000000000000000001c3e7b9df6e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd200000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b257846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000004d58159ed0e1fa7992970875e7860bb9755d4e040000000000000000000000004d58159ed0e1fa7992970875e7860bb9755d4e04000000000000000000000000000000000000000000000000000228d889a3d34a000000000000000000000000000000000000000000000000001b3c16d950ce97000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd210000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b258846d0182b0846d0182be8301361d94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000ee99f3fb8744dd05cbbd87104b7f088d194992ae000000000000000000000000ee99f3fb8744dd05cbbd87104b7f088d194992ae00000000000000000000000000000000000000000000000000016b3f8f9ae760000000000000000000000000000000000000000000000000016345785d8a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd230000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b259846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000076d696f8f3f5904aecf0f8982b82f88d80228f6700000000000000000000000076d696f8f3f5904aecf0f8982b82f88d80228f6700000000000000000000000000000000000000000000000000029a2f0df23dd9000000000000000000000000000000000000000000000000001c63e6326e3509000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd220000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b25a846d0182b0846d0182be8301361194508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000f2ed2042f3bf1c9e9ec3de5a79d6b8334e498d20000000000000000000000000f2ed2042f3bf1c9e9ec3de5a79d6b8334e498d2000000000000000000000000000000000000000000000000000016b3f8f9ae76000000000000000000000000000000000000000000000000000470de4df820000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd250000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b25b846d0182b0846d0182be8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000006bd99000b5874c02e666ced80905f038024296900000000000000000000000006bd99000b5874c02e666ced80905f03802429690000000000000000000000000000000000000000000000000000299cc8b064176000000000000000000000000000000000000000000000000001a7abad9e76168000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd240000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b25c846d0182b0846d0182be8301993a94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000008a8625919bfa16c58f5c3aa900c616be91955c520000000000000000000000008a8625919bfa16c58f5c3aa900c616be91955c5200000000000000000000000000000000000000000000000000016b3f8f9ae760000000000000000000000000000000000000000000000000001ff973cafa8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd270000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b25d846d0182b0846d0182be8301361194508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000000a6f2f5ad8954edbadfdb508e599cd26ab668b970000000000000000000000000a6f2f5ad8954edbadfdb508e599cd26ab668b97000000000000000000000000000000000000000000000000000155aa30e6ad80000000000000000000000000000000000000000000000000002386f26fc10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd290000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b25e846d0182b0846d0182be8301361d94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000c1ea3db638495d90bb778863ff5c2aaccfbbf421000000000000000000000000c1ea3db638495d90bb778863ff5c2aaccfbbf42100000000000000000000000000000000000000000000000000016b3f8f9ae76000000000000000000000000000000000000000000000000000c3663566a58000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd2a0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f482e708823ed285012a05f200852e90edd000825208949d0e0c96288a3ebdda1bd73523aeb8e262c6e679872714711487800580c0",
+ "0x02f482e70882402185012a05f200852e90edd0008252089468ad1fa00cb9d499b73e85c6449766374463b6b2872386f26fc1080980c0",
+ "0x02f482e708823ed385012a05f200852e90edd00082520894fa8d660cfa214e3071b915d5bb88c75834e9a15587470de4df82005080c0",
+ "0x02f84d82e70808846ba55515846ba55515828cbb94e5d7c2a44ffddf6b295a15c148167daaaf5cf34f80a42e1a7d4d000000000000000000000000000000000000000000000000000d51e75245751bc0",
+ "0x02f86e82e7080b846ba55515846ba5551582b4fb947d43aabc515c356145049227cee54b608342c0ad80b844095ea7b300000000000000000000000080e38291e06339d10aab483c65695d004dbd5c690000000000000000000000000000000000000000000000014546d1a4322aca3ac0",
+ "0x02f9029782e70801846ba55515846ba5551583023e759480e38291e06339d10aab483c65695d004dbd5c69874c1522a7a7ae9cb902642cc4081e000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000224ec2fcd3bcafda90000000000000000000000000000000000000000000000000000000064bc7ded0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004c1522a7a7ae9c000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000007f72e0d8e9abf9133a92322b8b50bd8e0f9dcfcb0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f000000000000000000000000de705471bb4414bfdabe09f52665914ed0e5725400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f86e82e7080e846548b1a4846548b1ae82b3e494e5d7c2a44ffddf6b295a15c148167daaaf5cf34f80b844095ea7b3000000000000000000000000272e156df8da513c69cb41cc7a99185d53f926bb000000000000000000000000000000000000000000000000001a711930b89000c0",
+ "0x02f86e82e70838846548b1a4846548b1ae82b4fb947d43aabc515c356145049227cee54b608342c0ad80b844095ea7b300000000000000000000000080e38291e06339d10aab483c65695d004dbd5c6900000000000000000000000000000000000000000000000c31f1cf83c3d8fb3ec0",
+ "0x02f482e7080a84623ed54584623ed54f83014cab94a02573c4ad15c16b48f10842aac9c9ea405b65a386d12f0c4c6000841249c58bc0",
+ "0x02f582e7080184623ed54584623ed54f83012779943c5b31b158dcaba76df46bd853c8af3ccf0f002287016bcc41e90000841249c58bc0",
+ "0x02f9027082e7080784623ed54584623ed54f8302d78c94272e156df8da513c69cb41cc7a99185d53f926bb80b90244ac9650d800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000104a8c9ed670000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f000000000000000000000000000000000000000000000000000000000000012c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064bc53ab0000000000000000000000000000000000000000000000001f9a0ef077f3ff15000000000000000000000000000000000000000000000000000444a1b61145790000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044bac37ef7000000000000000000000000000000000000000000000000000444a1b611457900000000000000000000000017cec49895644f84c50809d874e22feb923553d800000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9023782e7084284623ed54584623ed54f83035d6494272e156df8da513c69cb41cc7a99185d53f926bb8701e794b458ec96b90204ac9650d800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000104bfba6b22000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f0000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000000000000000000000000000000000000000012c000000000000000000000000a6dff484a46f90e5d03ff91e766ac70cf1e0eecb0000000000000000000000000000000000000000000000000000000064bc53870000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000001e794b458ec9600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041faa413300000000000000000000000000000000000000000000000000000000c0",
+ "0x02f901d082e7080484623ed54584623ed54f8307359694438670d41d5118003b2f42cc0466fbadd760dbf480b901a4ea5406320000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f000000000000000000000000000000000000000000000000000000000000012cfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed928fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed9dcfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed8ecfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed9a000000000000000000000000000000000000000000000000032c969648a3f809200000000000000000000000000000000000000000000000000060a24181e40000000000000000000000000000000000000000000000000001864ecab95235e0600000000000000000000000000000000000000000000000000026db13ab8909c00000000000000000000000038022d79892c57761a67ab251c7d70a55dbc87120000000000000000000000000000000000000000000000000000000064bc53abc0",
+ "0x02f8f782e7081084623ed54584623ed54f8302cbbc94c66149996d0263c0b42d3bc05e50db88658106ce8803a2956f887d21acb8c4f305d7190000000000000000000000009201f3b9dfab7c13cd659ac5695d12d605b5f1e600000000000000000000000000000000000000000000004dea22ce324f339a3a00000000000000000000000000000000000000000000004d8667c05d93ecf1c6000000000000000000000000000000000000000000000000039dee49db01a039000000000000000000000000633eb62c0a2a7c6ff9d03f801b1adfa5f02cc55b0000000000000000000000000000000000000000000000000000000064bc538cc0",
+ "0x02f86e82e7081384623ed54584623ed54f82b4fb945471ea8f739dd37e9b81be9c5c77754d8aa953e480b844095ea7b3000000000000000000000000272e156df8da513c69cb41cc7a99185d53f926bb0000000000000000000000000000000000000000000000000a1a9ce0f703af0ac0"
+ ],
+ "l2ToL1MsgHashes": [],
+ "timestamp": 1690062611,
+ "rootHash": "0x1f00ce5b9e15ff5f9cbcba3ab213ca2c22dabecbe5f8a902553aedef133152b3",
+ "fromAddresses": "0x46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b80c67432656d59144ceff962e8faf8926599bcf8e4edb277e41dc89ab076a1f049f4a3efa700bce880c67432656d59144ceff962e8faf8926599bcf827d967c1a5636fcea985f696525bb4f911dd5c59793560c4e66c450545bc54271f84b17d989c5ecbde705471bb4414bfdabe09f52665914ed0e57254d59c3b026e9abef3f744491462d369e4c27040b312e7301eef23a02c5162dce59c0b8ba264dc92e46abc316192b43dc64c79ec841ac9546f793022c420bae152846374d7572c1fcfe182e6bb5cfcb13117cec49895644f84c50809d874e22feb923553d8a6dff484a46f90e5d03ff91e766ac70cf1e0eecb38022d79892c57761a67ab251c7d70a55dbc8712633eb62c0a2a7c6ff9d03f801b1adfa5f02cc55b5636d6f8810951d686a22da016654f22efdda85e",
+ "batchReceptionIndices": []
+ },
+ {
+ "rlpEncodedTransactions": [
+ "0x02f9013282e70882b25f846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000007fbebf89ec31747099485233aacef5e3f39fd5950000000000000000000000007fbebf89ec31747099485233aacef5e3f39fd59500000000000000000000000000000000000000000000000000024523c543d459000000000000000000000000000000000000000000000000001b60f3f6387fe2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd260000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b260846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000a885747c9baade27c1a57d5747ad128e3d069bb5000000000000000000000000a885747c9baade27c1a57d5747ad128e3d069bb50000000000000000000000000000000000000000000000000002858ff455d411000000000000000000000000000000000000000000000000001b7919966d30ba000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd2b0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b261846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000e6dbe9a315aa775a52a749923accbc752665a2db000000000000000000000000e6dbe9a315aa775a52a749923accbc752665a2db0000000000000000000000000000000000000000000000000002a19565cc15f9000000000000000000000000000000000000000000000000001d2b3e94606485000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd2c0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b262846d0182b0846d0182be8301993a94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000001f657297fbe86fb4bf3ebe00d47a7f047cf5e8f40000000000000000000000001f657297fbe86fb4bf3ebe00d47a7f047cf5e8f400000000000000000000000000000000000000000000000000014d8f7e915b2000000000000000000000000000000000000000000000000000205466db74c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd2d0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b263846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000079e8da554811076d8df8975c2175c696c438592b00000000000000000000000079e8da554811076d8df8975c2175c696c438592b00000000000000000000000000000000000000000000000000032e61fb77d53b000000000000000000000000000000000000000000000000001bf05f22026dd8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd2f0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b264846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000084802571e30720a5dde1af48b84915d0a7d9d2a900000000000000000000000084802571e30720a5dde1af48b84915d0a7d9d2a90000000000000000000000000000000000000000000000000002c4c9673ddda2000000000000000000000000000000000000000000000000001b39ac02dbb9c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd2e0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b265846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000007fa3116b528f8941e6e8530e4a36d0a9d80574db0000000000000000000000007fa3116b528f8941e6e8530e4a36d0a9d80574db0000000000000000000000000000000000000000000000000003118f508e1ad3000000000000000000000000000000000000000000000000001ba80bb438c2f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd300000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b266846d0182b0846d0182be8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000a5d3299a38b5d0020a1690f11f8450c9e197d654000000000000000000000000a5d3299a38b5d0020a1690f11f8450c9e197d65400000000000000000000000000000000000000000000000000016b3f8f9ae760000000000000000000000000000000000000000000000000001cc6e836ae4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd330000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b267846d0182b0846d0182be8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000078874c29e4a3de39618e9bc39a239bd008c6472e00000000000000000000000078874c29e4a3de39618e9bc39a239bd008c6472e00000000000000000000000000000000000000000000000000014d8f7e915b20000000000000000000000000000000000000000000000000001d7cce57a2c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd320000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b268846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000570e9127026d8764bf8402fe10bfd7dcbb665ed1000000000000000000000000570e9127026d8764bf8402fe10bfd7dcbb665ed100000000000000000000000000000000000000000000000000028aaaa249f44e000000000000000000000000000000000000000000000000001c0b062dd167a6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd310000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b269846d0182b0846d0182be8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000d33e335e680198a0c141b8ae5769498a000fc998000000000000000000000000d33e335e680198a0c141b8ae5769498a000fc9980000000000000000000000000000000000000000000000000002f55c5d4b2dc2000000000000000000000000000000000000000000000000001be650d1ea042c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd340000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b26a846d0182b0846d0182be8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000004f6b45db2da225dc9dfa29eb258d3d63018c72060000000000000000000000004f6b45db2da225dc9dfa29eb258d3d63018c72060000000000000000000000000000000000000000000000000001429fc0610640000000000000000000000000000000000000000000000000002081e063b1e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd360000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b26b846d0182b0846d0182be8301993a94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000072d0a5f5f802ca3950d9ff8abf467e6b1465d43900000000000000000000000072d0a5f5f802ca3950d9ff8abf467e6b1465d4390000000000000000000000000000000000000000000000000001438dbfe44300000000000000000000000000000000000000000000000000001c6bf526340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd350000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b26c846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000051870c7d99f761b9297433be7256ea24290a0bbd00000000000000000000000051870c7d99f761b9297433be7256ea24290a0bbd00000000000000000000000000000000000000000000000000024e14ee9dad98000000000000000000000000000000000000000000000000001c058614b05ed4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd370000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b26d846d0182b0846d0182be8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000cc790b22ef435b76da0bb8d7c239fbf919655761000000000000000000000000cc790b22ef435b76da0bb8d7c239fbf919655761000000000000000000000000000000000000000000000000000298197090db88000000000000000000000000000000000000000000000000001cb13f9084f640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd380000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b26e846d1e961f846d1e962d8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000001e9a07ffcc98a932aa57fa57407833ec0adc6bb00000000000000000000000001e9a07ffcc98a932aa57fa57407833ec0adc6bb0000000000000000000000000000000000000000000000000002f0bfa7b63dae000000000000000000000000000000000000000000000000001c9da97501e879000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd390000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b26f846d1e961f846d1e962d8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000876199f2d7a3463179dc26c770fccb378a1dad22000000000000000000000000876199f2d7a3463179dc26c770fccb378a1dad220000000000000000000000000000000000000000000000000002f2078c9f9f67000000000000000000000000000000000000000000000000001c16b42e7835d2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd3b0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b270846d1e961f846d1e962d8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000a4734b099118455730d1a845dbdfd0725dc6a61a000000000000000000000000a4734b099118455730d1a845dbdfd0725dc6a61a0000000000000000000000000000000000000000000000000002fef25c863b85000000000000000000000000000000000000000000000000001c5f4e94f95862000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd3c0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b271846d1e961f846d1e962d8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000a211bd69c8a1400cb429a1ff92ad292489dc7f62000000000000000000000000a211bd69c8a1400cb429a1ff92ad292489dc7f620000000000000000000000000000000000000000000000000001429fc0610640000000000000000000000000000000000000000000000000000e35fa931a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd3d0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b272846d1e961f846d1e962d8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000c2f1d21cd496bea3a8361daaee75101df4d27ea7000000000000000000000000c2f1d21cd496bea3a8361daaee75101df4d27ea70000000000000000000000000000000000000000000000000001f0b2c94f77c0000000000000000000000000000000000000000000000000002e2f6e5e148000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd3e0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b273846d1e961f846d1e962d8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000b466fd02e3e151bcbce9c97fb0a9395889114df6000000000000000000000000b466fd02e3e151bcbce9c97fb0a9395889114df60000000000000000000000000000000000000000000000000002ddeda9078707000000000000000000000000000000000000000000000000001d11453af6ce96000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd3a0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b274846d1e961f846d1e962d8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000ac2314f552fe74262d6238bd4a6af9b910bd92b7000000000000000000000000ac2314f552fe74262d6238bd4a6af9b910bd92b70000000000000000000000000000000000000000000000000002c8d66ed8a114000000000000000000000000000000000000000000000000001ae9d2ef83c6e8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd3f0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b275846d1e961f846d1e962d8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000a4fe2ec308b39ded3fa9528efb798cd13a4f267e000000000000000000000000a4fe2ec308b39ded3fa9528efb798cd13a4f267e000000000000000000000000000000000000000000000000000300330dd90090000000000000000000000000000000000000000000000000001aa0fa68dac85c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd400000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b276846d1e961f846d1e962d8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000064531e1da97c2af6108a03d9c49a2e47b5bb71f100000000000000000000000064531e1da97c2af6108a03d9c49a2e47b5bb71f1000000000000000000000000000000000000000000000000000274797fa0c06d000000000000000000000000000000000000000000000000001c4a56c7f6b6d4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd410000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b277846d1e961f846d1e962d8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000059780ded40a5c540a536644dae651c54504dcb4400000000000000000000000059780ded40a5c540a536644dae651c54504dcb4400000000000000000000000000000000000000000000000000028b423ed3116f000000000000000000000000000000000000000000000000001b943596512384000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd440000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b278846d1e961f846d1e962d8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000524a06bada4d98aa7c96275b7d7130d3180b0ff8000000000000000000000000524a06bada4d98aa7c96275b7d7130d3180b0ff80000000000000000000000000000000000000000000000000002b55a3ca2b094000000000000000000000000000000000000000000000000001ab7f60b5414c4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd430000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b279846d1e961f846d1e962d8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000a3fa534de502a31714353d91f26e1e77f6084323000000000000000000000000a3fa534de502a31714353d91f26e1e77f6084323000000000000000000000000000000000000000000000000000139d8db69f480000000000000000000000000000000000000000000000000001f438daa060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd450000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b27a846d1e961f846d1e962d8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e09360000000000000000000000001d35a60b0b6815cac022f0ff5bffe4371a23ec460000000000000000000000001d35a60b0b6815cac022f0ff5bffe4371a23ec46000000000000000000000000000000000000000000000000000139d8db69f48000000000000000000000000000000000000000000000000000354a6ba7a18000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd460000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b27b846d1e961f846d1e962d8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000093f09eb91afcd7d4de05dbdfadde30d53d20580200000000000000000000000093f09eb91afcd7d4de05dbdfadde30d53d205802000000000000000000000000000000000000000000000000000241b57c404012000000000000000000000000000000000000000000000000001c4b5e4c48b40a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd470000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b27c846d1e961f846d1e962d8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000dd47d3db30809a107c1dd59417a61230458d278e000000000000000000000000dd47d3db30809a107c1dd59417a61230458d278e000000000000000000000000000000000000000000000000000139d8db69f480000000000000000000000000000000000000000000000000006a94d74f430000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd490000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b27d846d1e961f846d1e962d8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000863ca2c8116aaa8d8674a0b3491fb9fbb99ee706000000000000000000000000863ca2c8116aaa8d8674a0b3491fb9fbb99ee706000000000000000000000000000000000000000000000000000139d8db69f480000000000000000000000000000000000000000000000000000aa87bee538000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd4b0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b27e846d1e961f846d1e962d8301994694508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000012cb6f5eb0d90f8978deb060c46539e8b872550900000000000000000000000012cb6f5eb0d90f8978deb060c46539e8b87255090000000000000000000000000000000000000000000000000001398c01372ae0000000000000000000000000000000000000000000000000002aa1efb94e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd4c0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b27f846d1e961f846d1e962d8301995e94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000245722555190dc99ee569cdd1c5a67e05ce62dfa000000000000000000000000245722555190dc99ee569cdd1c5a67e05ce62dfa0000000000000000000000000000000000000000000000000002c38faa4ee925000000000000000000000000000000000000000000000000001c0e839c49bb12000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd480000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b280846d1e961f846d1e962d8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000da9a19e7831402175051adc494f67146e5827e43000000000000000000000000da9a19e7831402175051adc494f67146e5827e4300000000000000000000000000000000000000000000000000013498d5b048e0000000000000000000000000000000000000000000000000001ff973cafa8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd4a0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013282e70882b281846d1e961f846d1e962d8301995294508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e093600000000000000000000000099877f4429978df7552fde876e0eec345497444600000000000000000000000099877f4429978df7552fde876e0eec345497444600000000000000000000000000000000000000000000000000013498d5b048e0000000000000000000000000000000000000000000000000000aa87bee538000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd4d0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f482e70882402285012a05f200852e90edd000825208942aafe94148739a6e868e32cf8b1ca9fbc81e2d0987121e6c485ac03780c0",
+ "0x02f482e708823ed485012a05f200852e90edd000825208941981f7efb48591ee64bdaf0d61fe042e39fc12e3872386f26fc1001c80c0",
+ "0x02f482e70882402385012a05f200852e90edd000825208940d8c44854055f4becdfee63d2f6286548ace0c34871550f7dca7028780c0",
+ "0x02f9029782e70880846ba55515846ba55515830282449480e38291e06339d10aab483c65695d004dbd5c69871d21db47288000b902642cc4081e00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000010d92ea475e7ce7010000000000000000000000000000000000000000000000000000000064bc7def0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001d21db472880000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000046ec4bb184528c3aee6f1419e11b28a97f33d4830000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f000000000000000000000000a550c99253c5ee0136ac37eb610a28dfe2cc093a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9019782e7080a84623ed54584623ed54f8307a12094da4c3eb39707ad82ea7a31afd42bdf850fed8f418709a12f75a9e800b901649caf2b9700000000000000000000000000000000000000000000000000000000000000e00000000000000000000000009dd711b0cb4430f429231e5cb9940dbd1952a36f0000000000000000000000000000000000000000000000000000000001e13380000000000000000000000000000000000000000000000000000000000000012000000000000000000000000007039fb83798d979bc4697ab4e3e1bb715bf95700000000000000000000000009dd711b0cb4430f429231e5cb9940dbd1952a36f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005736869667400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000056c696e6561000000000000000000000000000000000000000000000000000000c0",
+ "0x02f901b782e7081184623ed54584623ed54f8321710694a02573c4ad15c16b48f10842aac9c9ea405b65a38701d704a97b9400b901845190563600000000000000000000000069421eeb27ca0941bf46e071098172a56b35df97000000000000000000000000000000000000000000000000000000000000006600000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000003d2a1800000000000000000000000069421eeb27ca0941bf46e071098172a56b35df9700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000001469421eeb27ca0941bf46e071098172a56b35df97000000000000000000000000000000000000000000000000000000000000000000000000000000000000002200010000000000000000000000000000000000000000000000000000000000030d40000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f86e82e7080484623ed54584623ed54f82b3cc94e5d7c2a44ffddf6b295a15c148167daaaf5cf34f80b844095ea7b3000000000000000000000000438670d41d5118003b2f42cc0466fbadd760dbf4000000000000000000000000000000000000000000000000008e1bc9bf040000c0",
+ "0x02f86e82e7083184623ed54584623ed54f82721794265b25e22bcd7f10a5bd6e6410f10537cc7567e880b844095ea7b3000000000000000000000000438670d41d5118003b2f42cc0466fbadd760dbf400000000000000000000000000000000000000000000006c6b935b8bbd400000c0",
+ "0x02f901b682e7080584623ed54584623ed54f8303b8da949e66eba102b77fc75cd87b5e60141b85573bc8e88695a0387c24e7b9018451905636000000000000000000000000f4c21df1cc8dfca3b064e9b3c9a0f434c3b9039f00000000000000000000000000000000000000000000000000000000000000b800000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000017d81d6000000000000000000000000f4c21df1cc8dfca3b064e9b3c9a0f434c3b9039f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000014f4c21df1cc8dfca3b064e9b3c9a0f434c3b9039f000000000000000000000000000000000000000000000000000000000000000000000000000000000000002200010000000000000000000000000000000000000000000000000000000000055730000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013082e7080b84623ed54584623ed54f83019a6894508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000ee99f3fb8744dd05cbbd87104b7f088d194992ae000000000000000000000000ee99f3fb8744dd05cbbd87104b7f088d194992ae00000000000000000000000000000000000000000000000000016b3f8f9ae760000000000000000000000000000000000000000000000000016345785d8a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bd230000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f86e82e7084484623ed54584623ed54f82b507947d43aabc515c356145049227cee54b608342c0ad80b844095ea7b3000000000000000000000000c66149996d0263c0b42d3bc05e50db88658106ce0000000000000000000000000000000000000000000000020ddba8ea3d826848c0",
+ "0x02f8b682e7081184623ed54584623ed54f829a3e94d9d74a29307cc6fc8bf424ee4217f1a587fbc8dc881af0e2108677632cb88429723511000000000000000000000000e4edb277e41dc89ab076a1f049f4a3efa700bce8000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000200325a9ead0f563f2ccf8c7afcf1f277a42d7bd16c676881b19cc96d467244f7ac0",
+ "0x02f9027082e7080a84623ec1c084623ec1c08302d77e94272e156df8da513c69cb41cc7a99185d53f926bb80b90244ac9650d800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000104a8c9ed670000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f000000000000000000000000000000000000000000000000000000000000012c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064bc539f000000000000000000000000000000000000000000000000821ab0d4414980000000000000000000000000000000000000000000000000000011925a4182e4820000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044bac37ef70000000000000000000000000000000000000000000000000011925a4182e482000000000000000000000000be8c5b0bdfb7b19c08fbad3c085b05da15cb721c00000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9013782e70880845f34f8e7845f34f8ee830390f994272e156df8da513c69cb41cc7a99185d53f926bb878e1bc9bf040000b90104a8c9ed67000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f00000000000000000000000066627f389ae46d881773b7131139b2411980e09e000000000000000000000000000000000000000000000000000000000000012c0000000000000000000000001e7bb53c1eefa4513b69fcbfa900b6bce051a77b0000000000000000000000000000000000000000000000000000000064bc539f000000000000000000000000000000000000000000000000008e1bc9bf040000000000000000000000000000000000000000000000000000000000000464229e0000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9047782e70820845d5a946d845d5a947783032773941a7b46c660603ebb5fbe3ae51e80ad21df00bdd1870d801472258000b90444a71c9b7f00000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000001b5722833b955e27a35a12eeab4886b650ad810b39e0de5a25fc177912a8bf476e2a99c9092df5e2f398069970b339375fd1aba8d53eb271e4f1fc1f749d7e5eb70000000000000000000000000000000000000000000000000000000064bc4de3000000000000000000000000b1ebe120c810ab46afcbcbdad7102096fb12634300000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b1ebe120c810ab46afcbcbdad7102096fb126343000000000000000000000000000000000000000000000000000bd011e3e0d0000000000000000000000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064c57e07000000000000000000000000e49cf1bbb229562aea4045133dfd244676b3acb8000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000b62c414abf83c0107db84f8de1c88631c05a8d7b0000000000000000000000000000000000000000000000000000000000000f1000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000955af4de9ca03f84c9462457d075acabf1a8afc80000000000000000000000000000000000000000000000000001599ba503c0000000000000000000000000000000000000000000000000000000000000000041dfb16404843d231528aaba3eaa2193cfc1ce0b72893909d63a87e98524f3b08c4b61dce555815a6366ef6e5c18f6c9832f9d13cc49d81e180429ac8babb9009d1c00000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f84d82e7080b845d4cdd5e845d4cdd5e828caf94e5d7c2a44ffddf6b295a15c148167daaaf5cf34f80a42e1a7d4d0000000000000000000000000000000000000000000000000006b54a2a2c0800c0"
+ ],
+ "l2ToL1MsgHashes": [],
+ "timestamp": 1690062623,
+ "rootHash": "0x0898f77ca827e6a3f6edc35c2d8a7ee7243739441dde6ce6db590b12f660601d",
+ "fromAddresses": "0x46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81b46ea7a855da88fbc09cc59de93468e6bfbf0d81be4edb277e41dc89ab076a1f049f4a3efa700bce880c67432656d59144ceff962e8faf8926599bcf8e4edb277e41dc89ab076a1f049f4a3efa700bce8a550c99253c5ee0136ac37eb610a28dfe2cc093a9dd711b0cb4430f429231e5cb9940dbd1952a36f69421eeb27ca0941bf46e071098172a56b35df972d584a6d566ea2cee64ea18dddbcddacbb7f681f8a0df28f173103e20de05093aa6e27829715c9dbf4c21df1cc8dfca3b064e9b3c9a0f434c3b9039fee99f3fb8744dd05cbbd87104b7f088d194992ae939a7ef73572e9218c7085aca6d894bc237f15e3aba9e8b0150c6e612d9d49ff8d0ddf9aa4160ee9be8c5b0bdfb7b19c08fbad3c085b05da15cb721c1e7bb53c1eefa4513b69fcbfa900b6bce051a77b08a9b4221a84bb39faa7d6fe0f7664efec9511acc5250939ab0e1aaf6680d54a2b2154e59a43871e",
+ "batchReceptionIndices": []
+ }
+ ],
+ "parentStateRootHash": "0x0db5a89c27cebc50b75b532636687ca2662de28a5341c5b3d4bcb2b4f4e0b9e4",
+ "proverVersion": "local",
+ "firstBlockNumber": 33118,
+ "DebugData": {
+ "blocks": [
+ {
+ "txHashes": [
+ "0x33b8ad2d347b76c3c78e5ecfe521c283ccccccd72c5195880dae9d00f5eab980",
+ "0x15d34713dbd2b309937e1f53e88c77ad50c6f68c96ecac2116b8f8b854381239",
+ "0x31347092ee78bd11582f48825b094e668933677bc1cc606cc317a0223741f699",
+ "0x9c68df3eb4bf8ed1d92d84ae53567574d591c8ddce51d8b34aaba6b2ccd16888",
+ "0xd82b87e4c4ee4d3b108c44d05b7b46513515a66a805b12d2f413f1631e5d8ce1",
+ "0xce4ec8617cdd1fba8463bb76f3767e155af6cc63db1557a697c20065693de49a",
+ "0x06e6ef03f7e67dcc60ae4160966bec0b5a058c92e918b44c62888ae0fc2d27d3",
+ "0x34611763b2a2f865b5df2264cdc3c049aae13e87ef13d77e21f4316fdd2c5c2c",
+ "0x7eed17b099e00c62c4d432f273ac2d22339040f94dcb9241968819ed8eec33d5",
+ "0xb7163f67cd7d2172418f5deb86a62c067f3b7bccc36d3ebc636d7982847f19ce",
+ "0xce223cd4c2cf0acc26af1041bcdbac3823fca09e09db5cd43581ea14c52ada2d",
+ "0x44d4ecff38101357ee1b74d088d0a9e3ce2852b737a108c848e121dd17973dc8",
+ "0xd83b699282e9e087e95f8554101c16da3ed1135d89a75374232b31867745a41c",
+ "0x5eab0f03880003241b85e2d68f3d2ba8dfb63cf67bff90b6a0c3e2fc30ba34b7",
+ "0xfa0abaf3bb7ce0afd7b0fd21ab78eb79372dfce8faa1d68f8f8dfd6ec83f8c66",
+ "0xafc599712a9a10f6311baabc78c270946698ee80e2598b8ad48e2fd6ae8aeb49",
+ "0xd310932c47ff9048b28e2d5082ce413504bf53d23425a15f278822b00720c2a8",
+ "0x5eab0f03880003241b85e2d68f3d2ba8dfb63cf67bff90b6a0c3e2fc30ba34b7",
+ "0x020f58d8a04fb1b64567234d18ee4e33e847b2ad6af1d486fc8c912afa82764b",
+ "0xe9135db60ba9cb6b1b3f81de31e8530c63757065d9e562033a90e43182c9c0d4",
+ "0xf9a34e4dd66ae14a2dd4c03c52cef0933e865f5dd3b5b311069afebb4914fc11"
+ ],
+ "hashOfTxHashes": "0x34a0bf05552d21800f3a4346c9fe2071767daa22f0fac5ecba21f0e356ec87aa",
+ "hashOfLogHashes": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
+ "hashOfPositions": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
+ "hashOfFromAddresses": "0x0387605e3468cc9ceecd4d5a9c7282899879523dd6d98418fd6a8aa787fdd4cc",
+ "HashForBlock": "0xbb50f9fb374ec77f1060aeea1fbddf182feee9c2c17664e3ae5dd9b13fdfb38d"
+ },
+ {
+ "txHashes": [
+ "0xe1e39f8c4731df31d1e9acc2178dcf7c410d839a708d42b049dc09870c144b8b",
+ "0xd22c7b46589c2c84168199b2522322983d6841dddb95a2db7b73eebb146d0989",
+ "0x251ff80c257ebc858547507fb7393efdb53be26bf6ff5bff60f301bedf5b94d2",
+ "0xabbd49c232d04197d5ec243326a11e5960da70d8fa7bdacb96b80c79035e59c6",
+ "0xb37b55c44982c87b90fc8c6894598697838f300f254a521efe453141e5c73ada",
+ "0xf8e6cb20fe34af5a9139c815493b1cf368ddb6f08cbe7ac87446e59beb45be2f",
+ "0xa252d9a0d4d5cab3f9fac8c3b2a01493aa217e20485b03a7c4e8434752210f08",
+ "0x2a0dfee0cd5a49395ed751442e1a48df0a98b144da058f631cca548860980da7",
+ "0xb27047284f817bfba3aa35c83a566b285f918b08da193cba0f57230ae653124f",
+ "0xb01d7705691db2c1fa48e2790704542a50b6fce60469626ae2ed21193fc5d9a5",
+ "0x6515e946304e6907a1c85b02938f9f5f896a7966b2479350ba3db075293fcf2c",
+ "0x1860df19170373188821b9e11815d7efb3150d291e6171d0c37fbaf5604e0ed7",
+ "0x2f168be2953e5b030453a8dc00624425a5f5e05fd8036182b2fa584136e47f18",
+ "0x781c200c88b39cf217b8484955e220b950fdd1b49752324ed6ab2952d5085d01",
+ "0x588ce20ef4021ff9242e1e9ff98457a668e3f52fd32a4f96df64cf7352b4e6ad",
+ "0xb85a6b9fce54bd60b07aa31c2edb5aab2e835d34b53f649ec4a650b056981eee",
+ "0x6d10ddd6d9d92e06c1254d7262ba5e34c817c0cacf7d36238502e0d3a918b33b",
+ "0x0ea5515f39e5fd39dc5bf490794f5e23270f7b020727972f3d24ec5e2e3c8e03",
+ "0x9a9fd46d45e2acd5b7f0c19194ddb1262a4de9474b0f662496339a99ad867e32",
+ "0xde8039adb7e0be4f80438e1b3c0a8a6ded538b602e90998083b98d8a04d434f2",
+ "0x1c459d9b02928bbef00cfdc579d4f69cc4152b0f12a49f92873ee579c5b3cca5",
+ "0x3e57e9ac19b84df49d9bcf1c90ac40f4607e63445f5d3e728d76d9b6e749e844",
+ "0x74632667d6f68741930f8f9c40f8a0762865fb9388e03ca70377aff3225e7462",
+ "0x033505da513d29c7f3f8a49ad7ca1e6215be9bf6ec0db3b1e85bce79a41634c7",
+ "0xa3f969c864f09c6d66a4208aa5773ebc018ee175153ec0e95a7d195214f443ec",
+ "0x47a10f787f700cde6dbb742f82b38b60595522a5b37a3e71142a464214d12005",
+ "0xb288d2887509b067194d38e298401648b70f7c6e073aec5035d946f0cc96eee6",
+ "0x55ce7f07dfc65be1997f829cc48c17fd648dda59a20f15b0838973c4acc2b6ec",
+ "0x9638728c2348797024b771a7f11a7f9ef24ac0ef47e0f6c07748b5d4087beca1",
+ "0x96695bab277b9a8df0e26c73dadaa2489f7f32d2faff836c39fd1cada7f53e45",
+ "0x30c3fa2924cea3a4b6971e54e7a9e063d03bfc007c381e98653c6058673fbe64",
+ "0x296b77172fe6ca7c93f3a27cad3469fe328f38a6b7d62bff8eb113c562811648",
+ "0xf4fb520848b590bfe47bcf93e9cbf61f77a3d9a2a46ce63711aef77e1252e845",
+ "0xe0c716e1abe1ced5af4a5d16068bfeefe71b6b4b0dafeb52dca0e4d62437c6c2",
+ "0x5504974a707746524142e4d87a85158b6e0c1669f8f7ca8c79a7b0c4ab736e5c",
+ "0xcceafae6719af922d536578ed8d9d80b6f9a034dc6d7220543edcc3aeb50b112",
+ "0xab5ffedfa4d3aebc9fd5b5711fc1333df8bda6e6c83d8795479006ca669913b9",
+ "0xf3d3fcdaf453a7ce863571db913b9e49e43f23f8bc21f9edc0e7ab08607c5b27",
+ "0x697fae72571c7445c528179adfb08739b50654713bcc21093ad0ed0bc2343415",
+ "0x6e26e4d0ae4d713d0d68b7da5cd53ee352f3aa020887014225a0991a2a047259",
+ "0x4a9b986c3b05b410aa1f304c914b0c89eecc71a8e23c0d235a81b3c57e76112c",
+ "0xd8b85bd548e76cbfb53679485bf4af4bdadea983abdb7c5e3958e945144f53ff",
+ "0xcb4d071107666f474f15da1571a8cba5a15d15b8fd6b4a1b7f1b0ba2f8e2a3f6",
+ "0xdd3d6ea98fdd96698f5f13c59e5cdb51f97d02991735ff7b5bc880af4834cb00",
+ "0x2259ed1a2d2ed8462338da523a4dbd29c38d1662de859ca583337663700834e9",
+ "0x6bfcea9251674921d5bf38e17f4ffd92a0a89d1b54910bee0e9e89ff53f49efd"
+ ],
+ "hashOfTxHashes": "0x23d15228532fbb55efd61aca03c88cf041fe29a5fa7bcf2bffec9384f0e1bfed",
+ "hashOfLogHashes": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
+ "hashOfPositions": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
+ "hashOfFromAddresses": "0xf1c8491fc9e3379cca6f1b00229d29f288eb5cf50bf41e2de97f7dbba03560b1",
+ "HashForBlock": "0x62ccbbbb86ef488aeeefa412012b550d385ac709ca4edd5549dcdd1d02c49e70"
+ },
+ {
+ "txHashes": [
+ "0x79c5454b996125abe3a3f1b2308e12bc1a05fbb18ccbd961d1e9ed5a60e99a3c",
+ "0xb48f595c9d8b0b3b430a3e18ce714b338d24e151444c802af42c160e83e1c023",
+ "0x1d33b875399ea2b977069a7a3af5af502816bfa57370cfde72b40b797e2cdf9e",
+ "0xf91e12a540222c88104dd71c2cb312651d5c6dffaf4a397b9b823ce006dc35f6",
+ "0xe22e54f211a9dc27fa8e306b760b41ee2430c7c5a6844b034275cd64d7f682cd",
+ "0xdecf2c8e07903f8fac04580c68b202b99b24eb56d6c9c34204718055169f35e1",
+ "0xc65db255285d080713ff417da3054e95df469126b79c0b4b1dd394ac17441683",
+ "0x066f6cbd57a6747706d1611bc48116ff088aa12174d9416a6ff8ff2c4afe33ab",
+ "0xbfc927e159210db0b7ee968257c58b94f55a203b9ff8f9cebf79e38d2b50de86",
+ "0xad05ed5e0b6f984e3ba8a022d0bbcd0787c0a4cda53d18d0de158c4bfe43972b",
+ "0xcfd19e2cfeebd293c1e087e61be6f9b632fbeb72e01b75af7827dbe8b041b9e8",
+ "0x56f4dfd08b403881832831981bbe7fcfbcd0e8da08994793457617793e63d597",
+ "0x8e67aea74c01a3ea7d16a6b9b47efb220e6816ba9e6029944925fba4be554306",
+ "0xc1809842c56bb4d49ef49c35c5c2016a3979f2f467f3eece542f814a1b308474",
+ "0x4309dbc805acd30e711c932cdffed396b097f18edb2a27310851e275ced82711",
+ "0xc2518d2b1ab1d703158e013adf992efc9696f5bc3b807cf3abed054a30529652",
+ "0x0c1bb65bdc1c24fa132c37504059fce12e3edfd3b9c5053797756ee25f07628d",
+ "0x0ecea1436ce18b7c66c6d9e0690506fb974393608267ddc64759d80365082843",
+ "0xf3d59eaeaf5f227e11c4e676b7789c0ad33940716e091bc5278075bd69019327",
+ "0x88e39cd7ba151a9f8f8dc5c3eaae99377f444b34d9be70404b19149efa6dfee9",
+ "0xa6768e6b696584e34764d4d853b5506ca3e21f6030ad576b5d40264382a7c634",
+ "0x604a18caa65f2241536c5f28cb8c5d2e1f2d8c686f9788137c4f24576a10cb52",
+ "0x2f5fea3ed9bbb33919946e2ea03719dcc12a1ff7267a84a2ec432f08b8a1723d",
+ "0xd209e9dd0197a7c10cffbb502b7f4af0714a4b50686d306a270f61e4855f6ebf",
+ "0x158f1510fe03b404484a2b8150ab0730ef8825854cc80977d4f8a3ea3fc7b759",
+ "0xef7487da45ff07d1ac218c7f192305b556b0d5b0906e4d940c5c9dadd50e3c8c",
+ "0x372ed95455b0932ce3bf40d6a8a35de722c626c513f3d92f4b774e8c89e119be",
+ "0x04b8ca4b2734f82a5327a905be3828d8689772d4104222b3f7f15eac2e41a868",
+ "0x2c15d3cc010c7cf663e30f34034743082d5dd002e5978c1f4781c4ff74cad8bd",
+ "0xde23d5166a4751afd2f80792929b9a699d9583d1c3ab84f327eeb09fbb720779",
+ "0xaa58f51031042cb889097716b22be59129f72504d5b9d6633e97a30ed5598c51",
+ "0xc74f177217d41c5b70ec6a523f277cf91a331a0450aac5b7dbc4ae18332c4652",
+ "0x67c43525a6d0d8b45c9c85f1ff260059338a0ad6ad64d456ca9dbfdecddff06f",
+ "0x49d8163a4f3f04ef21cf35fe7a82384ad926fcea2dcfe2a9f01825cc0fa1be2e",
+ "0xe8986d3c28f95a68a57ab7612a6dc801ce17c615cc9ca3dfce9ab6ab28e647ee",
+ "0x6e24f6034ffe2a99719606173f177f119a848f959976fba77d19c5d20523b196",
+ "0x3dd9defe080c662bece7e824f5af8cc6cebcb5d30c1023ea699f2021d2997c10",
+ "0x2f4b77b8a052f4b2fe9233104d6f51bb0c312f524bf315eac8c1544448f13340",
+ "0x7851ce66a67cef5c273839c5c4eaf2a4a1ff58e86c7b9342aa9743e4c462b079",
+ "0x6839a1b38d40df547c006156e5e82f03be4f3e3bfd9fa744cee1c3ec6c6c0b15",
+ "0x271c0dab45c8118a83fb964403263ca7fc4ab44ac86155732b6f66fa08233e0e",
+ "0xcfe46e3f690749455af944d0e9dd3195056898852da82c1298ba63bdf907a37e",
+ "0xb771169740aa9d7d2ed14d10d6b511c960135c877a0eb47cb41d557828268875",
+ "0xc120090ac715d0c88ed0652ca3c81727ebeed0a98b546c71db314decadd0a26d",
+ "0xd452e7c1936098aa268625f147892d1fb3e8e06ba4b9ab2b91a9950644f966c6",
+ "0x27e29d73fa87e9a21a2c2270031edadfeceb2f77392e2dc86023f814775a872c",
+ "0xd499a99d846b7012f01b53a47b8fd1701d89585c59179ea57a19f1de36b81e20",
+ "0x695bea22f78cc9fb72a191911ca33bf1183c7f98e9e34cb2a7e905cb75407c0f",
+ "0x265fabb3192edf158edf7074d30645e4dd1875c77c5d086089feb20622f5b88b",
+ "0x96e46b4eff69253f37399723307a09b3c9de0979db5c48d63e80bc418a57b5bd",
+ "0x48e0a7cfbf088dd37b003dab637c7431ac817220a4d0f9228d6371571e8281ca"
+ ],
+ "hashOfTxHashes": "0xd6407d1b3333a2efc5d37194c55eea9cb8e8fad53fbfd7d009ea3d5019c75128",
+ "hashOfLogHashes": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
+ "hashOfPositions": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
+ "hashOfFromAddresses": "0xf9f76121ce60580d7997cec266645cb12da15caf882ff0075829c2b7243ece91",
+ "HashForBlock": "0xf0e25e3c06a11aa02737a878098dbd5b246c46f29b4f45244c14024e8b22931f"
+ }
+ ],
+ "hashForAllBlocks": "0x0b7ef7c824a8ea7f49231802e8bfcb992f756fdd48e086c8f992f63caf1cedca",
+ "hashOfRootHashes": "0x7dcc64e8c3e428328d3f95778f7340d5fe39015147dc430bdbfb0901892edb6f",
+ "timestampHashes": "0x4662757f920ee71ddf31fa1a88c04f98fb63ef67a3032bc34c12a3ddacba363e",
+ "finalHash": "0x3041a1027bacc392df45fe2e0a35293974440621ecf1efca11e5359819869829"
+ }
+}
\ No newline at end of file
diff --git a/contracts/test/testData/FullLarge/rollup-2.json b/contracts/test/testData/FullLarge/rollup-2.json
new file mode 100644
index 000000000..30a716ee5
--- /dev/null
+++ b/contracts/test/testData/FullLarge/rollup-2.json
@@ -0,0 +1,118 @@
+{
+ "proof": "0x2f2080cd227a3c72933341c26c726aacea26bff35ad5f8273e611d95ffc043b50e7e71aca6df6878e4e1353c26f8bb410746184bc57452fbaae95fc59de913af0227a45d9f576aaec71396e420398a0fe0d12f1f4191fb7cc8f3be422514e1ef073dc11a32a6d17a2c820a70f86d5a0040ba8b6e2ee7c4d912ff26f409442c272799f8a9b8633f42c413a1031e1b152946ae4fc1f1ab0c348acc13252b15ac7414f486e0b043ae3593a43d90f38bfc3a951469260feeb5daf164e933104ac90420c9db02563c7afe7a5cba7b3b1cffbc5c7b075f7b3a0037875129f408185e7720e01317d2c06cc207940e499e29fec08294851d110271cb86a7d00a5a65ffe525cb0793ed3514101b812808c1ef902a00c2e869ac42e7b22db0e9a5c8d161e12c65c9bfb1e42b29531d7f570d7c53b831a676ada5bb3f02ea649575882c198f1833388b4c95df938d6b2999e6f3b53f67ba48885132aed2b4f84786467fba85172c6031d82a6df78794a178f8ae62c8cde9c6eede4d4f52988ad80ea8068a9621f5eb7aa8fafbf73d895b475cd9763f942cc360ecc46c66a4ad00ee23521d0412bfa2a184d36c51dda6b362ee785445c588f5fd7b2eb0f66ed9878ef628d589193867a89cc9b3613451ffbafd5cbf809cb205cc85e87e71900080895155d23b12e3bbe90b319099fcae12490acf454b86801e7da502208ac25c164d7172a5432dafb4f5c7efaabd6361e4aec0be75082b33ca93377190655249762ede6457d604c593ed8dc216f807ca65ff5b4c87b85dcdd6ccb3cd06a4f6da1773a0e9b7740166e12a746f92f41fbe27ce6fb66b22eea7bd9c78ed8e6512b9abb3d287a5b61189843f715cfe5e925faa2d5db2bcd8ed7fb48ae4c4b59d8242e8bb159225af1e651e0aa852d4b97701764ae3da38c22d9c36af7bcdb68f7fe512f75084d755204294f77feb63e7078225f216391b34557e68f3ffb06c0d5a20742e4ae4e49d2b101bff1b97000bedf086beccc51d173d9487d6483c9afa0aa6db6ecde623e4252189092f566d9e4ee1e0bb2dca64bb3b323df09c20557b962c728253a26165283d89361dfd8158b0c1b2d0c391bd0122aa441cdc14b0f24f0563ca41d1e8a81386bc180f14f0b643ebea53be00d3b302a7d7aa856923775995e1af3846d0090f008f9173df0b292431c8d2856d2f1a4e43ec8b617093119ceb991cdacaf01f29dfa4e17622996612b9b379d4dc553d942e217d0893da3a63f8d6e19b7b3ec0019e4e6df13884bbbff67d05a73be9dc9c429c63c6e9e3baec5ef478255f40a8",
+ "proverMode": "full-large",
+ "verifierIndex": 2,
+ "blocksData": [
+ {
+ "rlpEncodedTransactions": [
+ "0x02f482e708823ed585012a05f200852e90edd0008252089460fe7c6f554cc499eaf468d436f4985f31f57430873281606e20801680c0",
+ "0xf08084713fb30083031eee94a02573c4ad15c16b48f10842aac9c9ea405b65a386d12f0c4c6000841249c58b82e7088080",
+ "0xf8500184713fb3008302c8ba94eba2e9bd066dbce1b93a4007ef5431c234cbc7ae86763bfbd22000a41e83409a000000000000000000000000e0560dadfb44d789054f09f3a24dfc1771fb39b582e7088080",
+ "0x02f901f782e70806846ba55515846ba55515830497c59480e38291e06339d10aab483c65695d004dbd5c698727048bc1f6f5bab901c494ec6d780000000000000000000000007f72e0d8e9abf9133a92322b8b50bd8e0f9dcfcb00000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000006868dbe5fcf4261000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000007d43aabc515c356145049227cee54b608342c0ad0000000000000000000000000000000000000000000000011af3865a8f45923200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000027048bc1f6f5ba000000000000000000000000000000000000000000000000000000000000002000000000000000000000000010340719c67d9f66d2b49d6c510946e4723e93c80000000000000000000000000000000000000000000000000000000000000000c0",
+ "0xf901b202846b49d2008307395494db3bb6d5a8eeeafc64c66c176900e6b82b23dd5f86930c5d2b0686b9018451905636000000000000000000000000c1a8da2603bf579df012309768c021ebe8383d3300000000000000000000000000000000000000000000000000000000000000b800000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000001ab55f0000000000000000000000000c1a8da2603bf579df012309768c021ebe8383d33000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000014c1a8da2603bf579df012309768c021ebe8383d33000000000000000000000000000000000000000000000000000000000000000000000000000000000000002200010000000000000000000000000000000000000000000000000000000000061a8000000000000000000000000000000000000000000000000000000000000082e7088080",
+ "0x02f482e7080b84623ed54584623ed54f82fe0e94e5d7c2a44ffddf6b295a15c148167daaaf5cf34f87071afd498d000084d0e30db0c0",
+ "0x02f9013082e7081684623ed54584623ed54f830408dd94c66149996d0263c0b42d3bc05e50db88658106ce80b9010418cbafe5000000000000000000000000000000000000000000000000491166ff023ca5e100000000000000000000000000000000000000000000000000035502ab09541200000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000a0dce2188ef644caa8f93954f3989eb771b99e9b0000000000000000000000000000000000000000000000000000000064bc53c500000000000000000000000000000000000000000000000000000000000000020000000000000000000000009201f3b9dfab7c13cd659ac5695d12d605b5f1e6000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34fc0",
+ "0x02f8b682e7080384623ed54584623ed54f83017c2b94508ca82df566dcd1b0de8296e70a96332cd644ec873ecb9f85e3a747b8849f3ce55a000000000000000000000000940346ef1c275a5f5449a90830616fabce0a6bd400000000000000000000000000000000000000000000000000038d7ea4c6800000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f87682e7080284623ed54584623ed54f8303ee9d943c5b31b158dcaba76df46bd853c8af3ccf0f00228701e0e0dc5175f3b8441e128296000000000000000000000000000000000000000000000000000000000000009e00000000000000000000000000000000000000000000000000000000005acf4ec0",
+ "0x02f86e82e7080584623ed54584623ed54f82b4b794c5ff010aefbac255f5e2251660794feb4638191e80b844095ea7b30000000000000000000000003a5e791405526efadf1432bac8d114b77da3628c000000000000000000000000000000000000000000000000012cf746b3996619c0",
+ "0x02f86e82e7080284623ed54584623ed54f82b60f947d43aabc515c356145049227cee54b608342c0ad80b844095ea7b300000000000000000000000080e38291e06339d10aab483c65695d004dbd5c69ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0",
+ "0x02f86e82e7080984623ed54584623ed54f82b4ef947d43aabc515c356145049227cee54b608342c0ad80b844095ea7b3000000000000000000000000438670d41d5118003b2f42cc0466fbadd760dbf40000000000000000000000000000000000000000000000008c3bba747f062960c0",
+ "0x02f86e82e7080884623ed54584623ed54f82b4ef947d43aabc515c356145049227cee54b608342c0ad80b844095ea7b3000000000000000000000000272e156df8da513c69cb41cc7a99185d53f926bb00000000000000000000000000000000000000000000000d108bff2095240000c0"
+ ],
+ "l2ToL1MsgHashes": [],
+ "timestamp": 1690062635,
+ "rootHash": "0x2ffa2a815b21eca63bd8e30af2b3ad1fed2ed1a9d9538907a79c0603df3ea92d",
+ "fromAddresses": "0x80c67432656d59144ceff962e8faf8926599bcf8e0560dadfb44d789054f09f3a24dfc1771fb39b5e0560dadfb44d789054f09f3a24dfc1771fb39b510340719c67d9f66d2b49d6c510946e4723e93c8c1a8da2603bf579df012309768c021ebe8383d335f37e908b47eb126a03bf4acdf1f31450d2d0243a0dce2188ef644caa8f93954f3989eb771b99e9b940346ef1c275a5f5449a90830616fabce0a6bd420bae152846374d7572c1fcfe182e6bb5cfcb13124145a26832ad2c5aa985b1debf442e4a70554c1de705471bb4414bfdabe09f52665914ed0e572542054365058f0f0dfe53a3adb36d34a1849d0430a4f54744bfb176cd6f84778801da5a07611e941e6",
+ "batchReceptionIndices": []
+ },
+ {
+ "rlpEncodedTransactions": [
+ "0x02f9013282e70882b282846a476890846a47689e83017d9b94508ca82df566dcd1b0de8296e70a96332cd644ec80b90104491e0936000000000000000000000000007f41f598c9ee3a2e7e91158ec3887dfbbd2330000000000000000000000000007f41f598c9ee3a2e7e91158ec3887dfbbd2330000000000000000000000000000000000000000000000000000097c147684ca0000000000000000000000000000000000000000000000000001f9e80ba804000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000bcf50000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f8ef82e7082b8477359400847735940c83033fc094c66149996d0263c0b42d3bc05e50db88658106ce80b8c402751cec0000000000000000000000009dd6ea6f9d1fba5ed640651f06802e32ff45522100000000000000000000000000000000000000000000000000f06f81b2624271000000000000000000000000000000000000000000000000534e176575e010c600000000000000000000000000000000000000000000000000029a870e842d0900000000000000000000000009dfeb03893c810bac4713ee09ebde83138a7a320000000000000000000000000000000000000000000000000000000064bc53d5c0",
+ "0x02f9029082e70814846ba55515846ba555158302641a9480e38291e06339d10aab483c65695d004dbd5c6980b902642cc4081e000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000141952cbbb0c010000000000000000000000000000000000000000000000000000000064bc7e090000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000600000000000000000000000007d43aabc515c356145049227cee54b608342c0ad0000000000000000000000000000000000000000000000009275143d32c82d5d000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000007f72e0d8e9abf9133a92322b8b50bd8e0f9dcfcb00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000600000000000000000000000007d43aabc515c356145049227cee54b608342c0ad0000000000000000000000006810dba12317406828d64b07d34ad22338644bb300000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9029082e70803846ba55515846ba55515830250859480e38291e06339d10aab483c65695d004dbd5c6980b902642cc4081e000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000aa8e6a305a5f150000000000000000000000000000000000000000000000000000000064bc7e0d0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000600000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000000000000000000000000004db17dc48de15f5cc000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000007f72e0d8e9abf9133a92322b8b50bd8e0f9dcfcb00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000600000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000de705471bb4414bfdabe09f52665914ed0e5725400000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f86e82e70801846988fe1b846988fe1b82b4ef947d43aabc515c356145049227cee54b608342c0ad80b844095ea7b300000000000000000000000080e38291e06339d10aab483c65695d004dbd5c69000000000000000000000000000000000000000000000000201cbc28081cd660c0",
+ "0x02f9013882e7080384623ed54584623ed54f8302802e94272e156df8da513c69cb41cc7a99185d53f926bb8804855d8cb852ce13b90104a8c9ed67000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f0000000000000000000000007d43aabc515c356145049227cee54b608342c0ad000000000000000000000000000000000000000000000000000000000000012c000000000000000000000000b73bab39b5f7b709ddbb06c2eca6551f9303a7620000000000000000000000000000000000000000000000000000000064bc53b700000000000000000000000000000000000000000000000004855d8cb852ce13000000000000000000000000000000000000000000000020f141c50c9bda86d20000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9027082e7080c84623ed54584623ed54f8303a82894272e156df8da513c69cb41cc7a99185d53f926bb80b90244ac9650d800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000104a8c9ed6700000000000000000000000066627f389ae46d881773b7131139b2411980e09e000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f000000000000000000000000000000000000000000000000000000000000012c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064bc53cf000000000000000000000000000000000000000000000000000000001ee9885300000000000000000000000000000000000000000000000003d80b3b95e3f9bf0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044bac37ef700000000000000000000000000000000000000000000000003d80b3b95e3f9bf0000000000000000000000004de89c2ae04a31cc2dba3e2da11a38ddc43682b900000000000000000000000000000000000000000000000000000000c0",
+ "0x02f9029082e7083984623ed54584623ed54f830264329480e38291e06339d10aab483c65695d004dbd5c6980b902642cc4081e000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000001ac9ae3b2c767970000000000000000000000000000000000000000000000000000000064bc7e040000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000600000000000000000000000007d43aabc515c356145049227cee54b608342c0ad00000000000000000000000000000000000000000000000c31f1cf83c3d8fb3e000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000007f72e0d8e9abf9133a92322b8b50bd8e0f9dcfcb00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000600000000000000000000000007d43aabc515c356145049227cee54b608342c0ad00000000000000000000000012e7301eef23a02c5162dce59c0b8ba264dc92e400000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f482e7084384623ed54584623ed54f82fe0e94e5d7c2a44ffddf6b295a15c148167daaaf5cf34f87024071757c8c0084d0e30db0c0",
+ "0x02f88f82e7080184623ed54584623ed54f830222a094009a0b7c38b542208936f1179151cd08e294383380b864c299823800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000c7d8489dae3d2ebef075b1db2257e2c231c9d231c0",
+ "0x02f86f82e7080a84623ed54584623ed54f8301033094e5d7c2a44ffddf6b295a15c148167daaaf5cf34f80b844095ea7b3000000000000000000000000438670d41d5118003b2f42cc0466fbadd760dbf40000000000000000000000000000000000000000000000000086dc6b3bac8000c0",
+ "0x02f8f582e7080f84623ed54584623ed54f8302ab9a94c66149996d0263c0b42d3bc05e50db88658106ce86375928718153b8c4f305d7190000000000000000000000007d43aabc515c356145049227cee54b608342c0ad0000000000000000000000000000000000000000000000000195a2c30de3e2b3000000000000000000000000000000000000000000000000018d85e81cf8ed860000000000000000000000000000000000000000000000000000363dc65ac14c0000000000000000000000005029056a3581629953feba7cba2446b6057295160000000000000000000000000000000000000000000000000000000064bc53cfc0",
+ "0x02f8f582e7080f84623ed54584623ed54f8302bd9b94c66149996d0263c0b42d3bc05e50db88658106ce8629a862c316b6b8c4f305d7190000000000000000000000007d43aabc515c356145049227cee54b608342c0ad00000000000000000000000000000000000000000000000001314d071f8ff723000000000000000000000000000000000000000000000000012b31e3240d1608000000000000000000000000000000000000000000000000000028d3191b58d100000000000000000000000078ea6f35af55843f9d1d654f4a076ef435fa34690000000000000000000000000000000000000000000000000000000064bc53cfc0",
+ "0x02f8f582e7081084623ed54584623ed54f8302ab9a94c66149996d0263c0b42d3bc05e50db88658106ce8636c606f514f1b8c4f305d7190000000000000000000000007d43aabc515c356145049227cee54b608342c0ad00000000000000000000000000000000000000000000000001916c78ac7cf0c50000000000000000000000000000000000000000000000000189652e948eebf4000000000000000000000000000000000000000000000000000035ad962d9ec3000000000000000000000000cc6fc47b6a3a03b41f43fd33c699a1ac32e3b6910000000000000000000000000000000000000000000000000000000064bc53cfc0",
+ "0x02f8f582e7080f84623ed54584623ed54f8302ab8d94c66149996d0263c0b42d3bc05e50db88658106ce863f61aef7e2a8b8c4f305d7190000000000000000000000007d43aabc515c356145049227cee54b608342c0ad00000000000000000000000000000000000000000000000001d0829a2d78854200000000000000000000000000000000000000000000000001c7384f6a0059a100000000000000000000000000000000000000000000000000003e1d2b780c34000000000000000000000000d7c0fb08b29ea8bbca31b51d6d7bebadf288e2af0000000000000000000000000000000000000000000000000000000064bc53cfc0",
+ "0x02f8f582e7080f84623ed54584623ed54f8302ab9a94c66149996d0263c0b42d3bc05e50db88658106ce863d8d1586c10fb8c4f305d7190000000000000000000000007d43aabc515c356145049227cee54b608342c0ad00000000000000000000000000000000000000000000000001c31856249176a000000000000000000000000000000000000000000000000001ba12bad1eab6d000000000000000000000000000000000000000000000000000003c51f1417fc1000000000000000000000000326b931b50a190704341c4f832ebd7a75f4e86290000000000000000000000000000000000000000000000000000000064bc53cfc0",
+ "0x02f901b682e7080b846226c689846226c69383221e8f94a02573c4ad15c16b48f10842aac9c9ea405b65a386a1c0ee12c400b90184519056360000000000000000000000006abc316192b43dc64c79ec841ac9546f793022c400000000000000000000000000000000000000000000000000000000000000b800000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000003d2a1a0000000000000000000000006abc316192b43dc64c79ec841ac9546f793022c40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000146abc316192b43dc64c79ec841ac9546f793022c4000000000000000000000000000000000000000000000000000000000000000000000000000000000000002200010000000000000000000000000000000000000000000000000000000000061a80000000000000000000000000000000000000000000000000000000000000c0",
+ "0x02f8f582e7080e846226c689846226c6938302bd9b94c66149996d0263c0b42d3bc05e50db88658106ce86331b87492b38b8c4f305d7190000000000000000000000007d43aabc515c356145049227cee54b608342c0ad00000000000000000000000000000000000000000000000001768e69ac701199000000000000000000000000000000000000000000000000016f10af3d78113e00000000000000000000000000000000000000000000000000003215db9ebed50000000000000000000000002183ba8662f9f0e1bebffab11e31ba8cae53bcec0000000000000000000000000000000000000000000000000000000064bc53cfc0",
+ "0x02f84d82e70808846226c689846226c693828caf94e5d7c2a44ffddf6b295a15c148167daaaf5cf34f80a42e1a7d4d00000000000000000000000000000000000000000000000000c3663566a58000c0"
+ ],
+ "l2ToL1MsgHashes": [],
+ "timestamp": 1690062647,
+ "rootHash": "0x10eaf4f5d5411b3faa7ebfd4d08eafd71c0aa2967f2b65ffe2eea4083b51ef2f",
+ "fromAddresses": "0x46ea7a855da88fbc09cc59de93468e6bfbf0d81b09dfeb03893c810bac4713ee09ebde83138a7a326810dba12317406828d64b07d34ad22338644bb3de705471bb4414bfdabe09f52665914ed0e57254c624f1931ec68f1b0cf01a844db352f2be2bb293b73bab39b5f7b709ddbb06c2eca6551f9303a7624de89c2ae04a31cc2dba3e2da11a38ddc43682b912e7301eef23a02c5162dce59c0b8ba264dc92e4a6dff484a46f90e5d03ff91e766ac70cf1e0eecb944f99131d1ac989dccbe3fc8fb384438b9ddde652e085691b4ae78da51187a5e9d8381f08fe2bd35029056a3581629953feba7cba2446b60572951678ea6f35af55843f9d1d654f4a076ef435fa3469cc6fc47b6a3a03b41f43fd33c699a1ac32e3b691d7c0fb08b29ea8bbca31b51d6d7bebadf288e2af326b931b50a190704341c4f832ebd7a75f4e86296abc316192b43dc64c79ec841ac9546f793022c42183ba8662f9f0e1bebffab11e31ba8cae53bcec7dc130e21cf30c5e3adfc46664f55ff2c49502dd",
+ "batchReceptionIndices": []
+ }
+ ],
+ "parentStateRootHash": "0x0898f77ca827e6a3f6edc35c2d8a7ee7243739441dde6ce6db590b12f660601d",
+ "proverVersion": "local",
+ "firstBlockNumber": 33121,
+ "DebugData": {
+ "blocks": [
+ {
+ "txHashes": [
+ "0x3efac55a8dabbe5b488cf40a67b0d9b4400091b65d778e69753a9febf28ba3ad",
+ "0x2bb65c44a88817c33d827680ad7f019a786eaa2cfb2728f95d21fa6f9763a4c7",
+ "0x237b7d6fd52e10a9a324d66eac814a5fdc4ca9021f50b1b2c517bc0c1164e1a6",
+ "0x13e724a8efc49fee29bdba2d7d737aaef311703ca6bebea97c9b64361c201717",
+ "0xf56bda292071c06ac1fbbad2679ad9b5ed4e724aa41489510f7db59675799b71",
+ "0x24d9e924cf2a5f5400664f51d9dd5a1b9ab5f022d523439773e36ac2bb2425a8",
+ "0xe64f1c19876551d7fcafc0dc04d175bf43781f67ed0f68bc9d6c0b05bfb6e913",
+ "0xb2fd84dabe373607e40a08b5e81e6e2e3b114e303e6683fc5aa7c6f4c2d8d3cb",
+ "0xeae018f72b9aff41138ad78bf007473d9f0cebdf43a08a7f5122a06d05c9e41a",
+ "0x2a95a7b73a8e28684734d2e6c7802e89e787500fac3bdca8ee6337c29f112097",
+ "0xbd958770ade49e1c64c41e29f23af4ab865447e6cbca7c503258ac413227f5ea",
+ "0xc6c8c54a51bb09f64432b40e36e5efdb4956152c0c37d5c296188af466dacd7a",
+ "0xc7e4d04b8dd6491c1e17a43abe623837452e6cf66942ccb2e5b5ce04f76240d5"
+ ],
+ "hashOfTxHashes": "0x1181510f8850880c6512ba84973f570de679b6dd7d6a2673c8f3ee3164b38c63",
+ "hashOfLogHashes": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
+ "hashOfPositions": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
+ "hashOfFromAddresses": "0x3fb95664492db49a9c4f57e8aa4ee8fef44da97b4ba14cd9a1df9bca38e72961",
+ "HashForBlock": "0x6c20a02f2de954c3eec497f3afc390fd3e5f46690033712163b146bb16874dad"
+ },
+ {
+ "txHashes": [
+ "0xe408ad17e10f37365279d5966fbcd58fe6d11dba184c6fdacd6d33ebe950c334",
+ "0x5d4cc1caba2556b51813e2ac8d2ddc99dd57ac9cdeb6a24aec1bba190731f42b",
+ "0x5e86d43e7693471cfcb42eabc54a68a6b15f3a62e837e4d596a0ec68856e7493",
+ "0x30d791c89ac39d53ddaacfcceccc4bb94254d5aef89e82cb7472607b288cd955",
+ "0xd4b08bf89b28321bf937aedd5c601320ceb215c4b261411f0677eeb2045fba7c",
+ "0x4b19af47880b0e3210f6cee896c68ee7a50ad1a5705a28f2f5fb91f94a5ab2c3",
+ "0x53e260122beef7f1b158342517c2cb4d1f687f755ba0c9d17a17de48b5abcff0",
+ "0x4a29fe1effed49afb604fc521f8018b7ab524cae77953fc7b1ece969a5002449",
+ "0xed351aa2c65da3dbcab0f40e098284945cd8387f7ee17cccfa54c63ffe42d986",
+ "0xa088c5179dc084821e78fcf5c9c919b0afc588ef1e772d7e56e80bb14c7891fc",
+ "0x4b6f92186de0f92685bcc7d5ce76f354fb8bdb8fb42af797a95e930ef8047740",
+ "0x9166d9aa9fe71db1dcca6d0e9f4eaeaf62f5987bd1f3bd192f1f1e9fe8a61c5c",
+ "0x20d2aff42293b063dac160f72f28a4d50a356b28ab020edea517f495f03e5f34",
+ "0xfc3413d8b9a5a44b20a9b2bd778bc2a9d5309c897fb74320a067946282a2977b",
+ "0xd1a91cd524f06c71b92c8df87002bba7e2c5a10fd7e876f383040423bab6ae16",
+ "0xac02e1026769ee118cb82091c1a9ddf859ab91cd8e8711ed4a86d9b42830ded6",
+ "0x0dd6d66a03338e83d7789dc2ecd22e3fe90b39290feb827111e8b575f9589669",
+ "0x73248961e78aa90d28a2a225f54476580d4e45ec4a863ac664b254711421f2b8",
+ "0x40ffb1db14347b84fc4be1a9e20eade0bae32b1fa346a18a908b0ad4f6ec71a3"
+ ],
+ "hashOfTxHashes": "0x52b77f19ed8482dc71836c35e5afc787af4c91c845b2a69faecbcb29fc9fc905",
+ "hashOfLogHashes": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
+ "hashOfPositions": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
+ "hashOfFromAddresses": "0xbf40b5ca90779b817e50e8b4ae91c992c0fba6d0eef3fb23b52a076a1ea7e4ff",
+ "HashForBlock": "0x44ee6cd31dc7e7e80123f6bb3a64b00aa7ad0add4655fb026be8d53db72c83ad"
+ }
+ ],
+ "hashForAllBlocks": "0xa39af1a5682fd4462802511aee8e1c564e5c218d8cd2f6fda5ee9bb36d22ca6d",
+ "hashOfRootHashes": "0x609a61ed9c34be74036320ea49a390c4df31df88bf2e4b801babd8331480b103",
+ "timestampHashes": "0x99e954ed3802cb4418b96db5a9820eb88079c44e31c9ec455c762b2c68be598b",
+ "finalHash": "0x03add338a1b5fac261400153540f8266c971945823bfba1bdbaf6588fde40091"
+ }
+}
\ No newline at end of file
diff --git a/contracts/test/testData/Light/output-file.json b/contracts/test/testData/Light/output-file.json
new file mode 100644
index 000000000..d5360190d
--- /dev/null
+++ b/contracts/test/testData/Light/output-file.json
@@ -0,0 +1,229 @@
+{
+ "proof": "0x00cded37743ee9c060d1c1ecb4b0600b9d3770c591400bc72329ca1cdf1544cf1e369fcf032927f683b3f1c14d111241d66b2a24502eaa514b2e8eaddfb6d09617243d714160df0b6e6c09d41292b8f2e0f53980c4cb3b35ae6d86d69917a9760f27ff29e15aa95c01ee903d2c081fc602f21a5146b24a63454c8d7da7607c42223d73b2774246d60c5eddc254bbe881d670a86bcbe25fcf7076659b5407ceac18ae8582dff8b327e809487c9fd20635e781ab2c34f57f090ee805ae8daf4daa2cc49ca566b384483680cd181f589e428c34a19c35d4295261a84fad4a8c6e2a1a3cdb3a471aebb5b06fa5aaa4712a8135812cb92e254e7c764e34eeb15d66ff0c323fa0b23e83364de55a27efd86a37280d116e6401fc81ef6d2e2bc0c231f62f4e725b32a8316c6059fc791c9ae1111f09044b56401e5c4422ca910fc4732218f49d0ca4d60d2b5e78ea7c2044f0c195405b5568f19a8505a1d275cf8e24fe185021e8acc66f06ec5db26949e0cc59beabf36eb91ca6cbc962c87b8bed8aec2b77926c1ca6f889a110608078240e10c70c5a402889166d257200ce812cbfad2a348b46743d303c25543c1ec00ef994fd596a0f56fce5267a218d4ab7644a9007d7d05f0981c6cfc4d3e022fc22f39f52db0eb850801c15fe58be446a940c851919e779bed603fe9a5a9dc2ea2a3cede517143d1f0d0a3bfe2d5cfea2e73655170aa41dff3a69ccb3456e854adf5fb4511d8d9be9b9fcb45b8ebe6a5aa65c172db856e9688b5963bdfe5a84414c6ef857dfd6ee7be2fe4c9cc63f7c9d568d382dc336b32c2e27975d7e3cd20c503496d6a68fa0b1e3c5b86bfbf58600d2baf80eafc3763af9fdac33600e42195f72c9213dd5d73c0275f8ad4e9b130fcf22b12cab28e7a5e8c9dad0b153371d902b98a55e7ea95f1c338569ab0a627edd33e41c40ea57c2ab653af2e85e1fdb83667b6bec35cc46a6f0e9698e3e6a36ce63172559511113929d5840aac9c630bfa5180a932a14be47920447289d69e824202b0df361bdd6970ce2ea7e79ecaeca02974192dc7c651f9ed2046ca86538282f7008b8f3cef2d7c4a2200428fb48f5687d8dd21e29ac10019634cf647a88f665bb2c38344aee1489fc95d82e9375c95e0729cb20b04055634fb903851a0911611208b788a37459c10ca495306815967af2a84e1f9c59a5985aa789fbfe3d881c0518f7995461b904092245d02878f504b1b10a11b8561e1115510ef6e5e2c6ff2a1f5c74fe90fd40c46315ae8f87dedda4bc7e6d645e68e5c5839e388c7da53aab",
+ "proverMode": "Full",
+ "blocksData": [
+ {
+ "rlpEncodedTransactions": [
+ "0x02f8e8018083075d838309f4198302a9a194157c56e02b2e08d1767cdcb787ad0f395289f60683016b23b8be0011c10000ce0000003200000000ca00006b000000000000b283000000000000cb00600000005f000000007600000000008600009900000047000000000000009d0000001b000000000000f4000000000e0000f3000000000000fd000000842a00488e366700000000000000000000260000000000000000000022005400c7001a72000000004e00a26999000027000000578100000000920000000000eb009700b800820000000000c3000022cb00000000000050000000b14c00003e00c0",
+ "0x02f8ca01028302777982d450830d917f94310881c15ece5876b2aa333935b11d3675096d6c83089c5eb8a1300000490000000000000053000000db000000000000001f000000000000000000008600000000000000002d000000b11a00c700000000000000000000000f0000c50051003ce371a7004400e200000000ab0000000000000051000000000000000000000000000000a200000000000000008c009900030000000000003a0067000000000000000000004b4d0000be404116009d0000000000060000d400c70088c0",
+ "0x02f90289010e81cc82032f83098fe894c499a572640b64ea1c8c194c43bc3e19940719dc80b90264f4b476e1000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000115131eaf609de401a489ef07cac651fde707f7d460ab11de69f8a3dc4bb88a22bc888dc972650cefdee0e7a511c97959de6b4620ee65b1920057a3f00c640f098eb14beae1f27a66461c43a6e0684089345dd723677b851a45bc6abff5ed6e10e2b197cb041e8207b6df389488a5a7eec66fc602a5bb7bc8415f737511e1129bebc5166117fa6a852a0655d8e2ec73283d2c9f36eaf2733cbdb16c2a282c9f4c06d2acd31dec7175aac8c4122b86e73b9bd6b2a508ee9b3b85ab3417435049e40cc13aef8c506641531073f8cea41b7d493c2006179c9210df460afbf19b8d1fe0d1f55abf586640cee0fc330b4d99b0df5df5a6bbd59ee05895b51376b60fc22028b6bfb1f0c318cdb65702979922083db3098adfe7843d47d2df3854cdfa0f74244cf3ad0f8646f893eba5576c2c54fcb285ce95fd6b4d3b8f58c77b6e97da8e1df9236c89f264d4066f811a89aeba5479534cdbd503b406ddf99b9a8c3cd0b9705d65bfe4baf452e88847aa22686576487f5169e1b6ba5fde4cf32cceb2406ae3b93649d1f8a4ad011bd61eabc9a4e58977655cf148b9a913fbf54f652bbacc1c1dc631924d3cc153229ce238906c7c7d0494e06a09c9571dc413e91077928eb532f042e24ed41879aa522d80787bd85245506cf748074d07ea623a39fed057936a0b081eff2b6eba5ae4f8f2c1b05039aac5052e866103e2f17e08e934aa0a51d1faa293ef5d430f555a063e7eb5bc8babc0efea18872413fc27b2508bfc1c0c1aacd45945d31f12a35369fbeb0866f6f7b774a",
+ "0xf86c0383082810822e7e94082ac5cacc59ad30b6e76082ffeda64d79f96d7683058bc2b849a0000085b3000000000000000000000000e30000000000c1000000e4000000005100000000002c00000000bc00000000000006a6af000000000093000000000000000059ce00000000",
+ "0x01f90152010c830a0eb9830dd71794ffd98b285ec73e30514c2671a400960c1b30dc4a825148b9012c000000ef00000000000000000000000000190000008f000000000000000000000000f500000000000000000000000000f6ee000000004b7400000000ac0000613945820000980000000000000000000000000067000000000000530000610000b600600d00003b0000000000000000fe007787000000000000fd310000000000f100000000e4000000fc000000000000f10000000022000000000000bd0000da3e000000000017c2350024000000006d006100000000000089b4170000310000d29d00000000005b005e007a00000000000000000000000000000000430000950000000000000000000036000000000000000000009600000000000000db0000ca00000000000000000000f0000000000082d7000000000000000800000000a9b951000000000000251c0000c0",
+ "0x02f8b1010e8308306183021caa830c62f4942d184dc6a45229f02b1a93f046ff2027d8b0ade683092004b88794f600dc64b400db0000000000db00000000000000c60c080000c30000fb00000000000022bf0000970000000000003e0000000000000000006d00000000000000007a00eb0000000000000000000000000000000000009e0000200049ba00000000000000650000000000007c0000000068000044f600000000000000000000d4000000002900c0",
+ "0xeb0d830150a5823a8b94219999ed4233a411908ccd9a337e5902e860c111830f373e89006d470000aa000000",
+ "0xf9011606830d09238281fc9469fbf4e6abeeac862870e87167dd6af9e6321fa483085db4b8f3ee0000ef00000000003800dfc30000a3ff660000000000170000000000000000ac00c300000014f0000000000058000000009b000000010000c44ecf000020005800005e2000000000bc00000000370000000078008fe2000000000000000000008200000000d41500cc5c000000b00000000000000000d4000000000000000000e7d817130000000000000000f30000000000009f000000005a00300046000000000000d9d70000c900007b020000cdcf00000012000000000000a0006a000e000000d8000000000000000000db00000000000057000000000000190000676200000000bb0000d400005e006c000000000000",
+ "0x02f8d701068308a98e83061a6282b782941c4c50a4b9bf7ded8a17cdfcd7062f138c3f03ca830d78eeb8ae0000000000000000d8007e0000000b0000f60093c200000000000000000000004900000000000000f9000000000000000000000000000000b70000000000000000000000000000000000000000000000690000ec001b0000000000001800360000003b00e600e3a9640000000000000000000034ef5b000000002b000000000000617a0000680000880000000000490000008c000000000000000000c5f8270000002000e500007300ac000000bbc0",
+ "0x01f882010a830b5aed8301584894921089c10e87931ad3b76a89ec40263a1c122877830b382fb85c0000351f00000000110000000000006000000017000000004f000000000000a25f000000e3000000920000480000008d000000002b8caf7064000000000091d20000130000000000cf00000000001300000000474d00000000d20000c0",
+ "0x01f8e2010d830b6a848309b31894aee8096984a95e44410b78217250d399f92d5ce683046799b8bc0000000065000000ed0000000000a3ef000000000000000000000000d80000aa00000000007b000000000000f400da000000000000000000000000000000b9c10000000000000000ff0000e40000003b00c60000b000020000000000000000000000000046ee001a43005400a9000000001c0000000000000000005300000000002b0000003d0072000023000000000000570000000089610000005500232d000036000000000000dd000000000000390000f2000000004c00000000c0"
+ ],
+ "l2ToL1MsgHashes": [
+ "0x6ea262f0ea06bc87c6b2530748b21a967764fd7b48ea476598cf9da0c3e1a92c",
+ "0x86a3baaab37e292d58cb56f8ac58194664e343fb6a793be91a209e7d18bb886c",
+ "0xdd1504e564321451a6f11e9eb4c796c197209215e9c2e5c0a9fb6aa24d491cd4"
+ ],
+ "timestamp": 150000004,
+ "rootHash": "0xd82fc14edd8bd200ffe1aadfa616ec52d5175350e047d388cc2ed79d9c726439",
+ "fromAddresses": "0x090d5281fa1c37aa51b37c8fca6de8f8fb34f84c796918994f7459000d38eefc5b0588c58ca0b7af415c99d741e0c92789491b5a8e4b0dfd2ecd16c1c9fe128df53e8c18390b21388f8c66df4e08d58738ec48569e4e65427a132cb166a8c7696f3c5255e66d719cf4f2e05383efbf71c2871a3de408bca57c5dcc9e09d1d925da4d8b8cfa42f2d15a7844171fccd0fc305b1f663a59e07258e1907c9ab563eb6dae43c11299755d0b918446f08be670870ead3d1b75a2edb9eb11b8840b308fae6015ccf2a71e189234d84a7e13a05875f5d04564e0227b2208adc7",
+ "batchReceptionIndices": [
+ 2
+ ]
+ },
+ {
+ "rlpEncodedTransactions": [
+ "0x01f8ed0103830ea485830b550f945bad51939675bcb35778193c1724c26273629c2f830550a1b8c76c00000000000000000000001d000000000000009100008631000000000000590000000000000000000000bd009200000070004500000000008e0000000000e800000000001f0000010000000000000000000000004d0000000000000007600000e5330000000000000000000000e00000003b0000000000000000b8000000007a003a000060170000000000000000008a00000000005371d100fb000074003efe36e8de000000000000000000000000000000004621e6000000b4f7e10000000000002d000052c0",
+ "0x02f85501808304b81283051603830381e8941ff15283c317c0a111417c0f49b7803663816fd9830d5400ac00000000000000006b000000690000000000001c000000321f000000000000c900ff7e000000a30000000000c0",
+ "0xf901460e830b129782c54b94f751c1a9a465ea6569820bef1c2a456c7c9b5a9a82c0b4b9012312000000007800001b0000000000000000000039004e00b10000e2000000007600000000000000000000ddbe0000000000009500620000000000000020000000009316007d00000072fc00d450000000005b00003300004e0000000f000000005e000000000000650000ad0000000011008800000000da00000000f2001800f0000400110000000000006b0000000000eb7e0000000000f6002b00fb0000009000000000009d0000009400fd000000720000000048005b0000000000c70000000000002200000000009c4900e5000000cd91000000000000b50000050000000070b20072850000b74c00000000000000900000820000bf00005a00000000c1005500000000e300a00000250000000000b9008400000000000000000000000000007e00",
+ "0x02f89c01098302a477830d1aec8303dd4694a82e006b27c002bd7070579ac6dcaf8d0510ab1883095905b872d8009ccc0000000000000000000000000000000000009a7100300000000000008acf00c200f01a00ed00000000730000a90000060000000000410300003f00009e00000000000000000000a000000000000000b4005e090000000000a082003639ef00000000002000000000004d00007e00c0",
+ "0x02f9011a01018307d53f8305dde883030d70943168ef533dd2fb1b34b74245c1aaaa14a258208583050642b8f00000000023000000000064288b002900000000b700000000000000290000009f000000000000000000000000f700cddd0079000000b9000000000000b60000000058000000005f6d0000d40032000000000000000090000000000000000d009c00000025000000001b000000f1000000000000530000000b00000023004800000000d80f0000bbf7342c000000000000ad86000500000000bc00003e81080000000000003200b200003a0000cb000000300000c14d00000000110000680000000000c90000000094000000000002b000000000000000000083000000000000000000000000be0000034b000085000000c0",
+ "0xf9015c0e8307df22821517945b3c4a65b71486b8fa154f2057dcb56ce89713c283037a47b9013800000000000000000000000000000000320000a260007800009c00000d0000005000005a40000000008b00bf00bb00000000000000000000000000bb000000004533003500000093005400004c00000000e90000fc00000000c3000000f300004e5cd2000000005a00f6170000000000009300000000b02d00b9a700000c000e386e000000f800000000000047000000000041cb130000009400003c004800000000bc0000009600000000000000004d000000000000000000400000b10000000000230000000000001a00af007300f6000000c9000000cf000000000023620000000000001500e81d600000e60000000067000000000091000000f275b7009500ab00000100000000a9009b009400c6006f0000b4bb007c00000000006c003c000059000000000000000000f0000098ca009c0000630000",
+ "0xf8f50483043c45822a49941dee2393d44d272bbc31287f3c830be73b753b8d8304282eb8d20000ab36000000d20000000000b3a9000000000000000000000000000000003a00000000846e006d000000000000000000000000420000b10000000e02fae50000000000000000005e000000e0000000000000000000f1000079b4f6a00000000037000000000000320000930a7600370000000000f4000000000000000000000000000000002f000000430011000000005c000098000000000000000000000000000000000000000000ad05efb400000000000000a71200000000ec1f0000a200d3000000000000003100000008000a0000",
+ "0xf90283091c8303884094c499a572640b64ea1c8c194c43bc3e19940719dc80b90264f4b476e100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000011cb5493ea7b89cc16c2158680f0421e61525e08bb6bc811df2aa87722bbbe38551fef692a86786d9ce35077c54cdaaf33e417b2243a00537ba842c412d9b1bcf83e72776a54981f21bb1e74cbed80616a74847768df7b2db8059fc73ca1686bad6175e40e92d16fa499ed0e003b966337daded5453e8e521cfd559baaa3886bcc416e6c7e34c3424979a455e0498ad9a8e05f1854c01b129ba2d99808a0ea0eb36e0a52e100b51971512e94874573be0f863354a15a6d560aa3d400b680e67c205604ed23cf91f160652ccab48a6db11c5970a72c10c72f62ebe649c73047edbd4bda6994e0ddca78b504a47832e670963d4f68ed1802e15529e186a93e2e00cb995fae0bfc3cf5aa08354fff198a0ef7a924051bb750905db17dc48218898d688b921aacf8f25588367502393d48eb6d54c4741f21d030885237355710cf1d5e2b95822a529dc78f0966ac0cfa7e72c753ed324879063a6b67114732e3190c02c160e9b2660677262083cf6d8d3b8e2889e512c0f9550c9d2331f93ce0e0de7daf4e5c0460450706f03dd2b719c12388398b4f4452da4fdd75076b0069a99a54d3cb0755eb054cbe9d26219d385e8bce68d69958fed08b091e004bcb717a34bbdde3e11c4268413223e430ddcfdef3eed4594732fcf583e3ed302d0b071140338dac83ac90913d388eaadfa720d4aa9f37bf0444f8657095d2a1422720c1ea4d6ec4baf11e34f19e2e7c91b402e60d05be2909de08244b34c1911ebb8a8fbc6d8db21d55a7715fe7a8c2d2ab0b9fe0221f6f47df",
+ "0x01f9013701038304ff62830a441794471af8ad882859b9f0545d1f8950ea81ff16b037830751d1b901104300000000d0009e000000000000000000000000000036000000b60000710000000000060000000000000000000000000000f55a000000000b000000000000cb00002d00005000000000cc0000db00000000005b00000000000000008936c70000a4000000000093000000004d0000a200000000e6000000000000001000000000000000000000aac900b20040001f00000000a7000000ba0000009b63001a0056000000000000004200008d0000f1fc00005500f000ac0000b8000000000000002e3bd586a5b900000000000000000000000000003dfb0000000000009c00000000008e00008f00000000000000870061ee00000000000000000000498b0000bd0000000000530000009e9400002f88c0",
+ "0x01f9010d010c8304f0928307410b9499663d687c8879a4f6db52fb5e126552248ac5208302d5abb8e700ee000000430000090f0000cf0000002b0092007f00000000ca00000000000055210000940000000000b2003a0000e100ce0000c05000000000df0000000000005c000000000000000000000000000000000000000000000041000000750075007300110000009304a800710000000059f79000a800cb0000008a00c98700050000840000000000cc0000460000000a000000b80000000000000000000000006700000000000000290000000000a3730000f4ee00000000000000000000000000000000e70000b7000000bb93640000000000000000fab700000000d6000000008f0000000000c0",
+ "0x01f895010c830db28f83043e66946e5e70a4b96ae0d7c21501c115010f5c6c0085038308c267b86fd10097000000ae23000000000065000100000000003500a95b14002e00000000000000ce0012e7008700df4d000000e10000cb0000000000c4005a3411006e000000dc8a00000000005800000000c775200000000000000d000000000026000000090000230000000000000010d800c0"
+ ],
+ "l2ToL1MsgHashes": [
+ "0xe57c5878353ff003304edc9c2adc3c65fd4ec43e13b726271a02534afcdb3031",
+ "0x2b0884fac78637a6cd65127ae15b995f7e1fbeb08fcf580af8a69172b1d83fd9",
+ "0xfc0db6e73d41a7d19b541b2b042a7299befa56c557a8cb2cae5c1906930fff7c",
+ "0x12f3cfe200f7571af89cb2857c2d4c2c6ef0bf4cad80ad1a1fd7f8c66af1ec13",
+ "0x3873d87e1e199c8aa00bab61e0f0f078f1d097f4e2fb70996ff132c60814c546",
+ "0x940272559b668b833316c54d0e7c68851dd7ecf0d9f83b5147e166bcec5c70ad",
+ "0xbda1a3d540ce9314f1e85b67817ec3aed92491d5d3622a4626081491795bc33e",
+ "0xded7c02a7382c117b370c8fb23012bf602a82eb9024a86496380d1b816d3992b"
+ ],
+ "timestamp": 150000024,
+ "rootHash": "0xd0c275eb4e6a7c777360f0c5d942fc0b9678797d799e23f1a76028f54dc74f20",
+ "fromAddresses": "0x5894d7d83216dc93e5655d8ce37c7c386bf4bd2146fb439c5dd0dd7b91d1dce8c558a5ff473d0729a2dd5dd4842c34939fd085c5cd3d3d4bb7440966190583129e86d7fd25bc2cfaaa8502b4f1853feb3d32b0203bc5ee13d45539c8f90f503923f50285997918eb32f1f0d34c95bd45e2a10f8d592af12966e73dba54d4be0d6bdbb81967cc17ec46fc6382f19bdcddd18551bf1b9555fd5d83519fced948b8b07edf19c1bd8fe41158219328d8f7d0e26db4167ebb4571ce178dec144ea028b80d064953aa1cb995ff550c24dc1e7d77bc242460b88a93b347d195",
+ "batchReceptionIndices": [
+ 7
+ ]
+ },
+ {
+ "rlpEncodedTransactions": [
+ "0x01f901510101830321b1830a40da942ea24605b3dd92b9069f78a00f0ecc99fa08561983069b93b9012ace00310000000000000023000000b20000000000000000000000000000005d0000000000fe0e0000000000000000000000000000000000000000ac00000000e200000000ef00df00009f4300ba00004d4f000000000000b90000001c000b00000000009f09008c9e00000000004f000000000000000077000000f8f06b00000000b6004a650000000000000000000000000000000000eec500000000a3000000005200e2000000003b4300d7000000000038000000dd00004ff6266e0095000000e800000000000000e60000000000000000003b000000d4000300000000000000e46c0000090000000000009e000000aa004e00290080000f9b0000ce15000d0000000000000000d3000000790000000000000000560000000000000000000000007500000043000000c0",
+ "0xf9012501830a972c8301613d94a129f53bd63f91b0ade87f278cb941358fd8c752830bdce3b901008b004200000000d5000000000000000000000000f1000000170000004400000000002c510000000000000000002cfe0000000000009f000000002c00000000220089000000eb3d000000004f00001f00d800000000ed000000d800000000000000000000002a00000054003396000000d9270000a800000000000000000000000000370000000000000000005b900000560000f9002edf008d5600860022e6000000002f000000000000003feb00000003c1006e002d00130000080000c100fe00006a510000a30000000053000000bf000e00000000006b0000aa000000f472c17200900000910000e8000000000000004100000000000000000000d0ff0000",
+ "0x01f89d010e830bf98a830b90d99484251ddc66f9fa19490c26439e7325270e46e851830c7a99b8770000000000000000f6000000ef000000000038be000000000000004c00000061000000000000007adf006a00000000000097000049000000000000cc00000000005c00a900009f008c00000078000000000041000000004d00009700001312000054000000090000890000b79f006d8d0000000000fc00c0",
+ "0x02f9015d010e830ea65a830d077283014e0d9452d426c9b7616cefa81cbab33554ac87b0a8d1cf83068884b901320000640000000000ea00000000004d00004400ea0000450000000000be002300ad0000000000bb00b600fe2400007088480000000000180000000000ec00a50000005800004800002c2700af0000000099007e00001a00000065000000fa001200000000004ff6b50200000000009b31000000000000000036008500cf620000005d0000000000000000d120000088f3000000000000000000cd00003200ef7600000000000000000000cdfa650067000000000000580000924260000000000000ff38000000000000cb0000000001f0000000000000fa000000000000000000000026c50000080000000000000036140091000000000076000800e400a5000000846a00e300000000000500009600004200000000607d00000000cea63a0000ce000000d4000000000000005c00000000e3c0",
+ "0x02f8c0010d83032f6e8302f7b8830405fe94cdf4d0b9373ff20b4047c4c0e13eb6de885a28a9830a196cb8960000000000009a000f004e000000b800000000000000000000de0000000000007a000000f500e6ea00000000000000000000b1000000000000a400c4050000000000e64b00004700000000000000000000b4d4000000e60000000000007e002a000000130000000000000000000000aa0000000092000000000039000000f9a800000000000000000000af5a00005a00001b00000000c0",
+ "0x02f892010883057f03830bb3c6830c2af994c711544e6f97b61055985612782b17f7e3853c4d8308b4afb8680000de00000000001a00be00000000000000b791000000ac00000000b800000000ea0000000000f3004a000000d9de003383560000002bda0000000000000000000000000000000000000000a00f0000000000ed0000000000fb0000000000320000000051000000c0",
+ "0x01f8e901028307db24830c7c88945d35dda59aa3c9a260b9a370ea63f3811b14813283057796b8c36700000057000000000000cc000000001f6c002600000000000000000000670000000000cd79b300004b2800400000eee2000000da00007900000000000000007e0000bb0000000000000000000000003e9200d90000000000000000000000e8000000f00000390000ab10000021001bc517000000000000003758000000000000b6004d000000000000000000ca000000000d00004be0000000a600000000005800000000acca0000000000000000000000000000000000000000000000000000d800c0",
+ "0x01f90287010a820122830c6c3894c499a572640b64ea1c8c194c43bc3e19940719dc80b90264f4b476e1000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000110ec440d22b2ca1067c0dd36d803a04f625dbe04050b50b5a1a4d5427c2887e7285e9408bccabedbab27c76d2bb2d3e0cffa6af1ebbac5aec871aedc842ca6b390495eac22e95cbb4ab0bce2485977c8fa6019a4ec4e923fa61db9c34e8f99ff1c742f7794b5d71717bd55d5c5f0d8cc0d20b1b0af506794443e5c3e7f880dee199c28c50e9cc84aef76f6859f5da2794461decf77546a9e927d6f1c61d0fb4e7442d16e781c604d29f1f3dba7e1d69c03eabb5ea82f7089daf0093dcb7d21a2c98ce7bcec12484ab946138567b737ef4304fb7d96aaf47745d719e14de9fc0b684157eb26dec16775a3ebb809e4d129a96f954c1d468c2cf54be001f41232f88425a4f75e6ddd8f021c398579409d295b79009ea9458e68a3c21f8a3f2407eb1cd5d40c476417abde6701abea68f8ab62ba5cacd535af956808f50137e397c6e105bdf4d71e85d420692eb20f116d92dcc258f58a07d430f2b45a5d6d10599ab963c2013830299a3e60ec938e710cccf30879b926003e2a48ef137136db59e894a5c2d1fe8c5231e2b14253eddb4bde0ff9a394d5702403161ff60c64b084baad6a67b058af255b27bd75d962d18c09e4cb08a9143d40565685b26cb9c2f2ba279f05d1ef648d6efe6ef90f843a3777dc3e63fce3df9c9b2160eec8ca811aeee1d7e866ff3018f4960830e40eb9aaedd7f408df0d8d57b6a39ae52db32edb0de77d1293a02b67efda3f32c4eb3c08c4d4a47c5c59a9a6ef19c200943aafccecbc0fed76814957edf99e5456c5b0304840272052c65",
+ "0xf87f0c830d774b82cd789487af298a085826378c2bf549e1d103395f726088830cf121b85c0000840000000000730000000000aec6b5006b000016000000000e90005a0b0000000036000000000000000000001f70000000005f0000000000000003cd00000000c400000000000000000000000000a3000000002800005d000000",
+ "0xf86504830a525f822cb5945ecf95a2abea7d8ad8a5d9af089e6136dc2fee1f8305533eb8420000f7000000b90000000000f8000056f4004000fe0013000000000000de00000000eb00000000000000000000250b73000000009f0054000f000000000000000000",
+ "0x02f9015b01038303e01c830f245a830e41bd948537eca699ced809c6394ad33e649d780bb61f3683041bf5b9013000000000004400000000005800000094bf000000e5f100000000d600000000001e003500c80000000000003d0000000000004b58000000000000008c00000000000085000000000046b04900004e00006a00ec00287b000000009d000000000000732da100bf00e4590000000059a7696c00f456d00000794c000000000000000000000000000000f31100f00000000000b5000000000d0000000093000000009800000000000000000000004e0000000000006c0000140073bb000000270a00000000f100007d00000000009c00a80000007f0000a9002000000000da0000b929000078006500c00000250000006a0000000000f00000000000000000000000650000000000074b0000ae0000000000000000cf0000001a008300000000380020000000c30000000000000000158d5cc0"
+ ],
+ "l2ToL1MsgHashes": [
+ "0xb0c36c4c14c1f8c61574f9f1033861f799964b2a0327f032fd25202f399e4198",
+ "0x9d62913323e78c58c3712067e246253101b8b9dd46a3cbe3c761f3daf1499ade",
+ "0x8b0dc7a16e44b42d312ba8a71293235efa978397160cb1458330dcfe92e07539",
+ "0x251dab29f567462687344b46ffedbe8713f70db70ef1a620efd5060599f9fb0a",
+ "0xfff3a1dfc3a8f2e103469c187f327b5cdb72de6aa5645f6208f4721138729467",
+ "0x88932e900cb387b32c58516a1353654b67753423ce4f4b25bde547265f5d8f1e",
+ "0xc639a45c43201c06c497f86eb53dc769f1aa31d90cb58b429bad95eb26fb0826",
+ "0x1ac500979280e94ff69e503efaf3f24a75affd70006a78f93d83ebfcc0dc01f3",
+ "0xedcee120fc2fc8b47051913966c7f2bb11436fee6f874337f29e0cfbe6dca712",
+ "0x2bf1a72779b8b3660eb512213f24ab3e554060632acb915c806b2a51a45db02f",
+ "0x7ea4c4d02305e8f7025fc26d1f845451c57eefcc8722464e7629a9498edbb942",
+ "0x5808bea3b1b4979d24ce64ef8ea10d136817b2aba2706410129300380bd7c257",
+ "0x5f94536bb90732a224d6ac38a27c6f2d52a600622badab95273db612c0d51cd3"
+ ],
+ "timestamp": 150000046,
+ "rootHash": "0x1d02c12fae15605ecebf480cb6aee54f558871069afb056792df6ab5490376e9",
+ "fromAddresses": "0x9d4841b372ce799ae6c0b342a19231fc972915deeb07f8430e0a1d9c852854c2260525f7b749b32af95e1894e441a81f99a88f3cc54c4cd65b96db7c6d35f29524fa4d6760b8fecf6c1a105f10874210654698556558dd3ef9af949ef6286c00f31ebdf774e775a96cae7bc441e57a7bdf5c2446d91387c6ab5a5a7c1870ef1d333d8445b636f4b67411b35d1d49a3775d6e7c8185fc36f23f7b304774cbe1a9eacb998c89ce0ff94026d0e469f366c9dde60ed485c54905744d37f406ec561bfcfb24f81c5815b32a79792ba4cae063494aaf02a843a0ce9c4c9a4b",
+ "batchReceptionIndices": [
+ 7
+ ]
+ },
+ {
+ "rlpEncodedTransactions": [
+ "0x02f9011101098303f3fc8302fbba8304f4fa941111aaf8811fc83b4267c34051e0e02e24cb187f8302a241b8e751440000ea0000000000000000000000000000ea0036120000000000a200c60000000000000000000000000000cc00000000000000000000d800000000880000000000ecf60000e3000000bb000000000000850070c4000000030000000000009e0000000000000000ee00009300008d00000022450d59b494000000000000000000000000ce000000000000000000ac220000ef00000000d800000000001a00003b0000000000eb007000007a000000008e4900000000000000000000000000000000000000002e00000900003b006200ac0000e50000000000000000510000005a00006f0000c0",
+ "0x02f9015201058309567c83070d4d83043db694e0e92520ae3901f616bc8c02507e0f7fb1f6954183081b8bb901270078001a5b00d4005f4b0060009d000000007a3800ec000000625e0094000000000000000000f9c3000000006b00009800000000b700000038000000008000000000060500007000796e0000320000c700960000000000e100000000005ff800cc0000ae000000e200000000000000149a0061000000000000000000000000000000007b0000442800ab00000087000000000017000000000018000000210000cda900000000000000d44700002e0014000000008049000000000000fc0000000000b700003300be008a0000000000bf00000000000000db0004fddab62b0005589200ef0000000000000000000000000000000000000000000500c400630000850000de00008a0000009a00000000004e0000000000eb000000006e0000c3b1004f000037f700c0",
+ "0xf8770c8303dee983014d8694f662e8528737891dde58583db48eb9b7032ae7e483033fc0b853a20000000000000081126f00000000f0000000000000500000000000380000c43a000017000000e78fa70000007d00e1000000000061520000004500000000000b1fc0000000dc000000e6bc43000000007200",
+ "0x01f9013c010783066bcc830d1ac194ad3226731578059e8f1b12259d0e9c93c8c8321a83067b25b9011500000000000000270000000000df0000710000780029007c000000b87b0000000000000000b70000005fc76a000000000000000000000000d2e00000000000000000f9fe0000000000001000000000005c00d68600000051003b7800000000000091000000c000af00000000000000009700f20000007d0000003a0000000000000000000000000000af000000021b001200001300005b00d9950e00000000000000309a000000480081be000000000000000000000000bb00006b000088e300000000a900000000000200003796004d0000002400b60000000000000000007a0059002044000000006b0000ce000052000000000000000000000000be0000000400005b004897000000000000008d000000000000c0",
+ "0x01f8530102821af2830c0e61948e809fc3cd3cc653190cf356fc73078a30e872ee830debb0af007867380000c000a00000000000000000dcfc8900000000000000bd0000000000000000390cce000000c9b8000005c0",
+ "0x01f90287010c8203248308edc094c499a572640b64ea1c8c194c43bc3e19940719dc80b90264f4b476e1000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000119e54481c9d18a13656ef1cbc928251f82c131a993a3daec9d57125069dfab3b0134624e9138915e0c66348e3166eec468ce6889e07ece82112f70f8faafc09a0368ca86425d1b3be3be03483a8b90e09d4f14a3aac62f50d74ef3139d87a9491459266bf3b1644ac584baa90844943ea670fb77ccb72aaec013450efdb3f7e02cea0819cf372aead56f33c2572606cc8a518ec33b4db544bd50206edb2ed124735bb756b2aa107d55ff8c5be61f8d67f9fa61d136a9e20f824346d0630a41da3703096d730de1c3299033525ff4da4627ba51ea546a6fc4368605cc330f022cee8a3eab4808d631e910a7b1224b045af6312f8e16b6496e209d016c8348b8f293649c971b5039fbbf5fe96699eca0625b67c3866c743f81c98f61a50c7a6519197d2c0e328a969e8db60023e8dc02328e8e0df276791d198bc9fc57414dc1a4318bf37fbdd676066cadd89b6dd8d828f0f95aa02b3d3db2999e78bca2fbc519adf415f25dc2857742c644b90de9348ef83ce205aa61ae95d485f107086ed9d5c75b7f2bfec7542e735db4f22ef74ec973bcd07e1610f66b95c70454651c2d5e1c0220eb3f987055e87798ee1ed282cee707f65120cb3cb87612b6f89ec75d5bf9752b044c87daa80625d58a55b8143c277bdcfec37e0c11a1447965dee8a798930d789ee721aee2d2683d0e11b8330069aa6223085d4ceda1bf678d93a557a7e56a52a6eda9b2f6e9ab2dd28a10471f46863e1120f10440e51a43f05c627c285c05d9e04d9a439307be4ea07aa2fd38082e29382f0",
+ "0xf878078306c70983013d2c94ce39f873eb28503f8418e48c732b2e04d7c5c2fd821c3fb85500120000ca005700000000000000000000e0000000000094060000000000eadcd30000e60000300000000000008100000e00000000230000000000000000000000000000006d23003ea40000c40000008500004e00",
+ "0xf8c2088304e84b829d9e94c4beaf6bfa068511fad9d8ba967420e34733d021830d7cd7b89f5e00e911000000000083660000470000009700003b0000000000000000c4000000000000a50000c600001200001800000f0000004000970000007d009500000000920000000000000014b6000000b40000000000008de300000000000000000000000000db5900000000000000299800000000316b0000000081007cd8001600d30000000000ca8e0000000000006e0000005f0000000000000000000a0700",
+ "0xf9014a0283055459828aae9498e54856b72706c20df5afed2a287c16c2b393ea83039bafb90126000030000072d69655008d00000073a30000b2008000000000000000000000000000860000a60039000000002d00ba0061a300000500000048000000010000eeb0000047000000004c36a8fd00b05200000000004c0037000000538c00000000000000000000b5000000000000004800004ae00000000000000000000000000000004c0000008908000178000000007acff8000000000000e4b100000000000000009a0000785500050000000000000000000000000084373f000000008a0000160000da00d17f000000000000000030000000000000000000000000a90000000000000000230000000000000021001c00000000000074002d000000000000000000000000cc000000d37d00000067fd000033004700a7000000002b000000c4008000000000",
+ "0x02f90148010c8305a1aa823e85830c6b4094daa22d985b44c85ea0b3aa373aa3feb7b12a88a0830ddbd8b9011e0000000000003f00820000000000000000bd0000be000076000000a2008600000000000000000900dc000000009400000000006a000000de0000000000000000cd0000e400000000000000000000000000008c0000795800000000f93f006745009300cc0000026bb600000000003e00004f000000000000000029000000e70092000000260090007a000000000000000000000000000000000033000000ba0000000000000000d50000000000c80000060000ec92a0000000000000006b25001a000068000000000000d400000c0000000000001b009270d9000000f8006dbc00000000000000d4000000e30000001ab132000090000000002362003471000000000000a50000004b2300000000610000005c002a00000000000000acacc0",
+ "0xf8f409830f048483010a629464ba9c907cddc470c4c3d6aa46c1edd0a494715583030df4b8d0006a0000000000000000000000006c00b30000007d000000000000000000000200e80000002e00009c003d00cc0000adcdf3009e0006000400000000000000004000000000000000002600000000df00d873000000009a007d00000000000000dd006c00000015770000000323000000002d00000000000000000000000076000000720000000000b800000094bc004900000097100000000000dc000071006b0000000000a3000000cf2066000077000000007a00000000000000590000574d00000000257e0000005cdd00000000de"
+ ],
+ "l2ToL1MsgHashes": [
+ "0xe13b4e3efa0b75aa3b01784739da177af6569e7763dd410473bb35cb7d583978",
+ "0xbdef1a105ade2e49f84c06a41e8aad32d743fed6c73410800db5c5bf8b925d3e",
+ "0x0123eb6ce1e77477907481b6cb3ba9918c32a95fb0480f7c38e4fcea0dec8c0b",
+ "0x08993ebf157a94d02d36fb315e1cdf83797a9014c49da8abfd902b0d095f87db",
+ "0xcbe3d53e5d16770ea7b1860ca9e0a4812c6f5e76081b58e94d5731455fc0b438",
+ "0x92f84de73d13228f943c328295fdae396848bb40041de1f86eb789c149e17e45",
+ "0xcdd876812666fca2abac1ebd74f23976d147bf64829117adc4216ba1fda233c2",
+ "0xe70ae52f6c5dbbac485e2df10fc573ce11a38cd6ef8edbd18abfbe1a9a1319bc",
+ "0xc5e16c90f519b236deeb61e72acce756d424b0675459e1a3db892f3571efa8de",
+ "0x0dfdf2faea51bb184c046d14d167df1b69da3cdcf27a03eee5de8da6e797ab14",
+ "0xa7a23554a3f8c9dd53df00a20ce2aeb915309329a6b192124d2e408df6365606",
+ "0x0ebd56ca85079aca10e1bf1bf294d0df4fe51b12d650e5889048fa125469c2c4",
+ "0xbaaf221b433e32170f67a23a7056d69b763a4e999129f417259c1c8571809a48",
+ "0xec30ade3c88cc14c0d72f11df48c2440b078a304983d29f08e21722b2a2363bc",
+ "0xfa6967d53fce7f8eff8f712dfafb51f9cda80b0e9222c7949c0232fd30f6738c",
+ "0x39f15e308dd8060317b18e95e77b20e7f6c6cb880782e099bb2f10322bf78038"
+ ],
+ "timestamp": 150000049,
+ "rootHash": "0x11c632ae819c01fa4273277342a1e4abe6ce0e49ce4b071da695d47ffb4a0a45",
+ "fromAddresses": "0xe13bb3e398c33efc62a47456b60d7f012ffeb554c637695243ef98d33ea1b1a8f6fe1039cbed8f5617e726b753d0380cb4039d75d32c8e2cc9012f744e57b0adcf58b1d8f4fc5bce38cda1583ce473b836c5fc367883f97e9e44f682bc5173fb56581546180c25bea173d6e2d42fc779da22e3f58f2e25935a16937fe8f59b0dda2b51d20fb0d201347533dff9a3c8795fa1637a8f163b535fd327512e71c72859a2f36a835475d9b0b206a6a1cf5df50ce37a4d7bd104d2de90c99cafa0dbeda01fd36a9e013c2a219a2db04fe432b9042f9a547cef0e4f3e1935d3",
+ "batchReceptionIndices": [
+ 5
+ ]
+ }
+ ],
+ "parentStateRootHash": "0x0af1a2b824be146c558a3b0d8a5f04a80e9a097ff85ac1a1b9fbe7c860de87f8",
+ "proverVersion": "0.0.1",
+ "firstBlockNumber": 1,
+ "DebugData": {
+ "blocks": [
+ {
+ "txHashes": [
+ "0x4631fcfe46fb05f3940eee9b27c971847e83ff684e493b760352f87c267ef4a3",
+ "0x5d1dbd696fc6d7356cbf8c5dd11266a10d7a5803d49eeba59d8eded5d6878097",
+ "0xb674880967e1ab1f39296dd4a2c6bca370aa120e7a30491e233291f403788f77",
+ "0x4bd9c20d2e49d97593c213f65ba293640bfa5ad63bf4f7eac7ff4e19914abd59",
+ "0x4194a1f05568fe60105e63a897e27c30bae0bbe3b79bbcd72253657b33ef957a",
+ "0x7fcd35cac504c334a2e9d5eca40a751fb3f1c7372f3ba8579942a271eb72a7a9",
+ "0x44532538d4ab7a1132fe0cb3cfb90d831182f23f13db917d514a02a51161870f",
+ "0x609f40737ff13c51b7e52f471da877332010bc7d929673f5b1bd7baab854932b",
+ "0x5d6f1600d10e11730f70b37c9de1f29f776815d1e05e9dcfeab0cc9a82202320",
+ "0xfd9c9c53bb387d0a965d2e297ab72026a20afd9e5bfa2be4201acbfabb532914",
+ "0x1a037bef5feb12c0e3456eb2287c8701787289e7e43ff862398eac76b05e42f2"
+ ],
+ "hashOfTxHashes": "0x0319175c8bf8ee33d4295769b07522bacbd6b9032d05e8ddce6a8287d1f166c5",
+ "hashOfLogHashes": "0x552a627ef7aaa083396c2576333dc0f85ce64d9b1bb0b41a7ee02682dfa4cc63",
+ "hashOfPositions": "0x405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace",
+ "hashOfFromAddresses": "0x27c8152d1e3a2f7e95088ec5defb2f6e15436a69297e64c53a3e3c433535e665",
+ "HashForBlock": "0xd0c6fb5059bd5c82eb938690df288319d3a055bd00c55b2429cf07b6240df2ed"
+ },
+ {
+ "txHashes": [
+ "0x9e7ff22620016029f58e528edebbeb9faec1a0adda45218ed29becf248fd699c",
+ "0xc508cee9b97302451e7e7fa2efcefcaa335040f5649e3ffb980c91a34248cfa5",
+ "0x58eb80b67e528c9c3c80fcba46721708d170a303adfa5c3d3b1a07ba14e40c61",
+ "0xf316c675feac4aa25cd64cd0239c987fc83d2d02b3b8309368050b475f2ff15d",
+ "0x655e21c5d3a991f2fbdb9fcb5420b369adba24afb6cdb5bb92a291494355a8b1",
+ "0x58a105184e9be867550b49ca5039195b4709a39ab947022b23639cfc8dff0bfd",
+ "0x706e53ca8a36f77abfc975c9bd0d6074a83a715901d538bb7b45283296ee9261",
+ "0x1e70d57d10b9004d6fe6f2fc3910b182ad85f1493c260d33754dfd33e1a60c4d",
+ "0xc06696ffffb236346a4ea4a7cfc7dfe984f950a666c1119629907778cd178525",
+ "0x685d27dd9d5bec106e4d49e5d077fd6713685208efe651852d0cf5fdb17c73bf",
+ "0xe751be2d2c1e70566e89db8ceb7f024010cf64fb75257cc62d9b063623403aac"
+ ],
+ "hashOfTxHashes": "0xea4c138495134cc47e58d3e976848a8ec7e4ed8105c6db43619978f8e0336aa1",
+ "hashOfLogHashes": "0x2b52ecf47e62171ef33051f8776b04635b8e7f653e4210023705720841d09b52",
+ "hashOfPositions": "0xa66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c688",
+ "hashOfFromAddresses": "0x5fdb5ba628e53b2ae8b1bac57b5e0828ecc0f5bf43421ef6fc421bf53420b39d",
+ "HashForBlock": "0xad47ef74879c6397ec3baad796ff15aa23b92654247c12884b59a4613165ed91"
+ },
+ {
+ "txHashes": [
+ "0xcf4ec7b20f1c07a73e2e632c956ef7ad81c914ea8290c60c3b81a468305c2798",
+ "0xd959146fd31d18be2ae3612e865e3b0ba57c85a34cd213a00aae9dc52c17a9d3",
+ "0xac9989e6e2be68a9b3883a263dfbbce479d32407c72914c144440afe3b3d8096",
+ "0x383592c2031d66328b30000394cd5828ceaa9d11fb467eb0082e6b3d579df969",
+ "0x331776e07dcc627ecb785d54ffd905f641e800cc2c3dec6b5107c900876c502e",
+ "0x61d98430c69b5a1cf6524b63f09cefc3bdfa3a9d26061134662f7ddf48885cf5",
+ "0x745b5fb65d8ffcf1404fc11762a113fdc5fe0be5df3e6d445a43fe14141decb5",
+ "0x2fa3ab47428492e2fbb9685721af1bac63252247dfa255801a588b2707650f32",
+ "0x84762f1056e985ca3566f1997a4ec2e993de8d5b2f0b1309444411870626c940",
+ "0xab248f4ecf07488de545db6c4e7962679913457024a4c36cfcde3b9660487203",
+ "0xb8c97d91cf95ec9d62e311d44de1d5cab6e3297a8e48c7de33a5609c5275fa6c"
+ ],
+ "hashOfTxHashes": "0x658cb158ebc0205cdae95b2daa6f31c7ca59253023a7063027a47a8e065e689f",
+ "hashOfLogHashes": "0x728697daa10aa03a8aec3ebc114b3304f1c5db5653c84a79d4104e062fd962c2",
+ "hashOfPositions": "0xa66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c688",
+ "hashOfFromAddresses": "0xfa8da1c7caf5d42b35f1533a9c4bb0f0bdc9d187095d777ee4c897c13460fcde",
+ "HashForBlock": "0x7dd48f72dcac14922f86782ab62fbd60d9042e044d70c1e9da478724a16cb4bf"
+ },
+ {
+ "txHashes": [
+ "0x1ffc24142c023f4cb6ce78a2db5866847c2d1afbe76ee88db20ff036ccfecb7a",
+ "0x693af87c7dbf5692bc902852201e2ff11ba4701932a994c439cfaadf3a3ac300",
+ "0x88772f56a578e2941b4965f133c6e501958c0e1ab2071665c5c3cb2cb121df46",
+ "0xe41a41bb1fe6badfec1a9fe00ad2285a8b54f8f8eacad2488c7d03885feee7a1",
+ "0x138ba32906cc02acb22c02b2b3fc49112cd1ef26ae2df00ca86f4fcd8a68f68f",
+ "0x56d976c7f0f2030eb4d91630b0edcacf08c3b1ffb02a5e04a59cad2ef72b68bd",
+ "0x4f8920975f81b0fe7ffa143e1704e20630d054dbd8588f0111a2f787eee2679f",
+ "0x860e9ff5c069f1c53893c18f18dfae51a0395687306e73bc49e098228b695cfa",
+ "0x7f66826c84220abea0cb633a54b8abc7c083147c1eb207271c41a205773810a8",
+ "0x3b51f3ba5c0d3655a1de1a5c6dfbad22eda62d9f42b026a8332e2cf8d5d3adf8",
+ "0x1267e1971ce31077fa25f880c2c3a872de5005478e1f33083428bdee9727d006"
+ ],
+ "hashOfTxHashes": "0x93800650fd3d07a58ffc1e343c3cd296d854ac457a981c2e689173d4c1399b36",
+ "hashOfLogHashes": "0xcbf233e14e75219f79372973c4fa4d400319d9e1994f1c8f979b32a643ae5fb4",
+ "hashOfPositions": "0x036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db0",
+ "hashOfFromAddresses": "0xa616fd0e05e41c9655726b303acdb9ae47a6bb6f81e1b9b00ce80aa34eb938e9",
+ "HashForBlock": "0xc1b38d671c834a566227d29f4b259de431a1c56c6cfb846cbda25b521d30193b"
+ }
+ ],
+ "hashForAllBlocks": "0xa6b658528cc1d303c7f3c96fe561e4363e0ea7fa8c2e3322d6d9e8652928ff60",
+ "hashOfRootHashes": "0x2237887ddffbf266ab1d1f3c28e19bb2c8ede77f394549ec826ef13a0f8d8fa6",
+ "timestampHashes": "0xb783d7024aa2ab96355289ca397e29356bf7a815ae55f79487a40eaa29125a1c",
+ "finalHash": "0x0e06c1ac6c1c41f193e5db17a0c2af992544e5d4465d6b87951c885c92707244"
+ }
+}
\ No newline at end of file
diff --git a/contracts/test/testData/Light/rollup-1.json b/contracts/test/testData/Light/rollup-1.json
new file mode 100644
index 000000000..d5360190d
--- /dev/null
+++ b/contracts/test/testData/Light/rollup-1.json
@@ -0,0 +1,229 @@
+{
+ "proof": "0x00cded37743ee9c060d1c1ecb4b0600b9d3770c591400bc72329ca1cdf1544cf1e369fcf032927f683b3f1c14d111241d66b2a24502eaa514b2e8eaddfb6d09617243d714160df0b6e6c09d41292b8f2e0f53980c4cb3b35ae6d86d69917a9760f27ff29e15aa95c01ee903d2c081fc602f21a5146b24a63454c8d7da7607c42223d73b2774246d60c5eddc254bbe881d670a86bcbe25fcf7076659b5407ceac18ae8582dff8b327e809487c9fd20635e781ab2c34f57f090ee805ae8daf4daa2cc49ca566b384483680cd181f589e428c34a19c35d4295261a84fad4a8c6e2a1a3cdb3a471aebb5b06fa5aaa4712a8135812cb92e254e7c764e34eeb15d66ff0c323fa0b23e83364de55a27efd86a37280d116e6401fc81ef6d2e2bc0c231f62f4e725b32a8316c6059fc791c9ae1111f09044b56401e5c4422ca910fc4732218f49d0ca4d60d2b5e78ea7c2044f0c195405b5568f19a8505a1d275cf8e24fe185021e8acc66f06ec5db26949e0cc59beabf36eb91ca6cbc962c87b8bed8aec2b77926c1ca6f889a110608078240e10c70c5a402889166d257200ce812cbfad2a348b46743d303c25543c1ec00ef994fd596a0f56fce5267a218d4ab7644a9007d7d05f0981c6cfc4d3e022fc22f39f52db0eb850801c15fe58be446a940c851919e779bed603fe9a5a9dc2ea2a3cede517143d1f0d0a3bfe2d5cfea2e73655170aa41dff3a69ccb3456e854adf5fb4511d8d9be9b9fcb45b8ebe6a5aa65c172db856e9688b5963bdfe5a84414c6ef857dfd6ee7be2fe4c9cc63f7c9d568d382dc336b32c2e27975d7e3cd20c503496d6a68fa0b1e3c5b86bfbf58600d2baf80eafc3763af9fdac33600e42195f72c9213dd5d73c0275f8ad4e9b130fcf22b12cab28e7a5e8c9dad0b153371d902b98a55e7ea95f1c338569ab0a627edd33e41c40ea57c2ab653af2e85e1fdb83667b6bec35cc46a6f0e9698e3e6a36ce63172559511113929d5840aac9c630bfa5180a932a14be47920447289d69e824202b0df361bdd6970ce2ea7e79ecaeca02974192dc7c651f9ed2046ca86538282f7008b8f3cef2d7c4a2200428fb48f5687d8dd21e29ac10019634cf647a88f665bb2c38344aee1489fc95d82e9375c95e0729cb20b04055634fb903851a0911611208b788a37459c10ca495306815967af2a84e1f9c59a5985aa789fbfe3d881c0518f7995461b904092245d02878f504b1b10a11b8561e1115510ef6e5e2c6ff2a1f5c74fe90fd40c46315ae8f87dedda4bc7e6d645e68e5c5839e388c7da53aab",
+ "proverMode": "Full",
+ "blocksData": [
+ {
+ "rlpEncodedTransactions": [
+ "0x02f8e8018083075d838309f4198302a9a194157c56e02b2e08d1767cdcb787ad0f395289f60683016b23b8be0011c10000ce0000003200000000ca00006b000000000000b283000000000000cb00600000005f000000007600000000008600009900000047000000000000009d0000001b000000000000f4000000000e0000f3000000000000fd000000842a00488e366700000000000000000000260000000000000000000022005400c7001a72000000004e00a26999000027000000578100000000920000000000eb009700b800820000000000c3000022cb00000000000050000000b14c00003e00c0",
+ "0x02f8ca01028302777982d450830d917f94310881c15ece5876b2aa333935b11d3675096d6c83089c5eb8a1300000490000000000000053000000db000000000000001f000000000000000000008600000000000000002d000000b11a00c700000000000000000000000f0000c50051003ce371a7004400e200000000ab0000000000000051000000000000000000000000000000a200000000000000008c009900030000000000003a0067000000000000000000004b4d0000be404116009d0000000000060000d400c70088c0",
+ "0x02f90289010e81cc82032f83098fe894c499a572640b64ea1c8c194c43bc3e19940719dc80b90264f4b476e1000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000115131eaf609de401a489ef07cac651fde707f7d460ab11de69f8a3dc4bb88a22bc888dc972650cefdee0e7a511c97959de6b4620ee65b1920057a3f00c640f098eb14beae1f27a66461c43a6e0684089345dd723677b851a45bc6abff5ed6e10e2b197cb041e8207b6df389488a5a7eec66fc602a5bb7bc8415f737511e1129bebc5166117fa6a852a0655d8e2ec73283d2c9f36eaf2733cbdb16c2a282c9f4c06d2acd31dec7175aac8c4122b86e73b9bd6b2a508ee9b3b85ab3417435049e40cc13aef8c506641531073f8cea41b7d493c2006179c9210df460afbf19b8d1fe0d1f55abf586640cee0fc330b4d99b0df5df5a6bbd59ee05895b51376b60fc22028b6bfb1f0c318cdb65702979922083db3098adfe7843d47d2df3854cdfa0f74244cf3ad0f8646f893eba5576c2c54fcb285ce95fd6b4d3b8f58c77b6e97da8e1df9236c89f264d4066f811a89aeba5479534cdbd503b406ddf99b9a8c3cd0b9705d65bfe4baf452e88847aa22686576487f5169e1b6ba5fde4cf32cceb2406ae3b93649d1f8a4ad011bd61eabc9a4e58977655cf148b9a913fbf54f652bbacc1c1dc631924d3cc153229ce238906c7c7d0494e06a09c9571dc413e91077928eb532f042e24ed41879aa522d80787bd85245506cf748074d07ea623a39fed057936a0b081eff2b6eba5ae4f8f2c1b05039aac5052e866103e2f17e08e934aa0a51d1faa293ef5d430f555a063e7eb5bc8babc0efea18872413fc27b2508bfc1c0c1aacd45945d31f12a35369fbeb0866f6f7b774a",
+ "0xf86c0383082810822e7e94082ac5cacc59ad30b6e76082ffeda64d79f96d7683058bc2b849a0000085b3000000000000000000000000e30000000000c1000000e4000000005100000000002c00000000bc00000000000006a6af000000000093000000000000000059ce00000000",
+ "0x01f90152010c830a0eb9830dd71794ffd98b285ec73e30514c2671a400960c1b30dc4a825148b9012c000000ef00000000000000000000000000190000008f000000000000000000000000f500000000000000000000000000f6ee000000004b7400000000ac0000613945820000980000000000000000000000000067000000000000530000610000b600600d00003b0000000000000000fe007787000000000000fd310000000000f100000000e4000000fc000000000000f10000000022000000000000bd0000da3e000000000017c2350024000000006d006100000000000089b4170000310000d29d00000000005b005e007a00000000000000000000000000000000430000950000000000000000000036000000000000000000009600000000000000db0000ca00000000000000000000f0000000000082d7000000000000000800000000a9b951000000000000251c0000c0",
+ "0x02f8b1010e8308306183021caa830c62f4942d184dc6a45229f02b1a93f046ff2027d8b0ade683092004b88794f600dc64b400db0000000000db00000000000000c60c080000c30000fb00000000000022bf0000970000000000003e0000000000000000006d00000000000000007a00eb0000000000000000000000000000000000009e0000200049ba00000000000000650000000000007c0000000068000044f600000000000000000000d4000000002900c0",
+ "0xeb0d830150a5823a8b94219999ed4233a411908ccd9a337e5902e860c111830f373e89006d470000aa000000",
+ "0xf9011606830d09238281fc9469fbf4e6abeeac862870e87167dd6af9e6321fa483085db4b8f3ee0000ef00000000003800dfc30000a3ff660000000000170000000000000000ac00c300000014f0000000000058000000009b000000010000c44ecf000020005800005e2000000000bc00000000370000000078008fe2000000000000000000008200000000d41500cc5c000000b00000000000000000d4000000000000000000e7d817130000000000000000f30000000000009f000000005a00300046000000000000d9d70000c900007b020000cdcf00000012000000000000a0006a000e000000d8000000000000000000db00000000000057000000000000190000676200000000bb0000d400005e006c000000000000",
+ "0x02f8d701068308a98e83061a6282b782941c4c50a4b9bf7ded8a17cdfcd7062f138c3f03ca830d78eeb8ae0000000000000000d8007e0000000b0000f60093c200000000000000000000004900000000000000f9000000000000000000000000000000b70000000000000000000000000000000000000000000000690000ec001b0000000000001800360000003b00e600e3a9640000000000000000000034ef5b000000002b000000000000617a0000680000880000000000490000008c000000000000000000c5f8270000002000e500007300ac000000bbc0",
+ "0x01f882010a830b5aed8301584894921089c10e87931ad3b76a89ec40263a1c122877830b382fb85c0000351f00000000110000000000006000000017000000004f000000000000a25f000000e3000000920000480000008d000000002b8caf7064000000000091d20000130000000000cf00000000001300000000474d00000000d20000c0",
+ "0x01f8e2010d830b6a848309b31894aee8096984a95e44410b78217250d399f92d5ce683046799b8bc0000000065000000ed0000000000a3ef000000000000000000000000d80000aa00000000007b000000000000f400da000000000000000000000000000000b9c10000000000000000ff0000e40000003b00c60000b000020000000000000000000000000046ee001a43005400a9000000001c0000000000000000005300000000002b0000003d0072000023000000000000570000000089610000005500232d000036000000000000dd000000000000390000f2000000004c00000000c0"
+ ],
+ "l2ToL1MsgHashes": [
+ "0x6ea262f0ea06bc87c6b2530748b21a967764fd7b48ea476598cf9da0c3e1a92c",
+ "0x86a3baaab37e292d58cb56f8ac58194664e343fb6a793be91a209e7d18bb886c",
+ "0xdd1504e564321451a6f11e9eb4c796c197209215e9c2e5c0a9fb6aa24d491cd4"
+ ],
+ "timestamp": 150000004,
+ "rootHash": "0xd82fc14edd8bd200ffe1aadfa616ec52d5175350e047d388cc2ed79d9c726439",
+ "fromAddresses": "0x090d5281fa1c37aa51b37c8fca6de8f8fb34f84c796918994f7459000d38eefc5b0588c58ca0b7af415c99d741e0c92789491b5a8e4b0dfd2ecd16c1c9fe128df53e8c18390b21388f8c66df4e08d58738ec48569e4e65427a132cb166a8c7696f3c5255e66d719cf4f2e05383efbf71c2871a3de408bca57c5dcc9e09d1d925da4d8b8cfa42f2d15a7844171fccd0fc305b1f663a59e07258e1907c9ab563eb6dae43c11299755d0b918446f08be670870ead3d1b75a2edb9eb11b8840b308fae6015ccf2a71e189234d84a7e13a05875f5d04564e0227b2208adc7",
+ "batchReceptionIndices": [
+ 2
+ ]
+ },
+ {
+ "rlpEncodedTransactions": [
+ "0x01f8ed0103830ea485830b550f945bad51939675bcb35778193c1724c26273629c2f830550a1b8c76c00000000000000000000001d000000000000009100008631000000000000590000000000000000000000bd009200000070004500000000008e0000000000e800000000001f0000010000000000000000000000004d0000000000000007600000e5330000000000000000000000e00000003b0000000000000000b8000000007a003a000060170000000000000000008a00000000005371d100fb000074003efe36e8de000000000000000000000000000000004621e6000000b4f7e10000000000002d000052c0",
+ "0x02f85501808304b81283051603830381e8941ff15283c317c0a111417c0f49b7803663816fd9830d5400ac00000000000000006b000000690000000000001c000000321f000000000000c900ff7e000000a30000000000c0",
+ "0xf901460e830b129782c54b94f751c1a9a465ea6569820bef1c2a456c7c9b5a9a82c0b4b9012312000000007800001b0000000000000000000039004e00b10000e2000000007600000000000000000000ddbe0000000000009500620000000000000020000000009316007d00000072fc00d450000000005b00003300004e0000000f000000005e000000000000650000ad0000000011008800000000da00000000f2001800f0000400110000000000006b0000000000eb7e0000000000f6002b00fb0000009000000000009d0000009400fd000000720000000048005b0000000000c70000000000002200000000009c4900e5000000cd91000000000000b50000050000000070b20072850000b74c00000000000000900000820000bf00005a00000000c1005500000000e300a00000250000000000b9008400000000000000000000000000007e00",
+ "0x02f89c01098302a477830d1aec8303dd4694a82e006b27c002bd7070579ac6dcaf8d0510ab1883095905b872d8009ccc0000000000000000000000000000000000009a7100300000000000008acf00c200f01a00ed00000000730000a90000060000000000410300003f00009e00000000000000000000a000000000000000b4005e090000000000a082003639ef00000000002000000000004d00007e00c0",
+ "0x02f9011a01018307d53f8305dde883030d70943168ef533dd2fb1b34b74245c1aaaa14a258208583050642b8f00000000023000000000064288b002900000000b700000000000000290000009f000000000000000000000000f700cddd0079000000b9000000000000b60000000058000000005f6d0000d40032000000000000000090000000000000000d009c00000025000000001b000000f1000000000000530000000b00000023004800000000d80f0000bbf7342c000000000000ad86000500000000bc00003e81080000000000003200b200003a0000cb000000300000c14d00000000110000680000000000c90000000094000000000002b000000000000000000083000000000000000000000000be0000034b000085000000c0",
+ "0xf9015c0e8307df22821517945b3c4a65b71486b8fa154f2057dcb56ce89713c283037a47b9013800000000000000000000000000000000320000a260007800009c00000d0000005000005a40000000008b00bf00bb00000000000000000000000000bb000000004533003500000093005400004c00000000e90000fc00000000c3000000f300004e5cd2000000005a00f6170000000000009300000000b02d00b9a700000c000e386e000000f800000000000047000000000041cb130000009400003c004800000000bc0000009600000000000000004d000000000000000000400000b10000000000230000000000001a00af007300f6000000c9000000cf000000000023620000000000001500e81d600000e60000000067000000000091000000f275b7009500ab00000100000000a9009b009400c6006f0000b4bb007c00000000006c003c000059000000000000000000f0000098ca009c0000630000",
+ "0xf8f50483043c45822a49941dee2393d44d272bbc31287f3c830be73b753b8d8304282eb8d20000ab36000000d20000000000b3a9000000000000000000000000000000003a00000000846e006d000000000000000000000000420000b10000000e02fae50000000000000000005e000000e0000000000000000000f1000079b4f6a00000000037000000000000320000930a7600370000000000f4000000000000000000000000000000002f000000430011000000005c000098000000000000000000000000000000000000000000ad05efb400000000000000a71200000000ec1f0000a200d3000000000000003100000008000a0000",
+ "0xf90283091c8303884094c499a572640b64ea1c8c194c43bc3e19940719dc80b90264f4b476e100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000011cb5493ea7b89cc16c2158680f0421e61525e08bb6bc811df2aa87722bbbe38551fef692a86786d9ce35077c54cdaaf33e417b2243a00537ba842c412d9b1bcf83e72776a54981f21bb1e74cbed80616a74847768df7b2db8059fc73ca1686bad6175e40e92d16fa499ed0e003b966337daded5453e8e521cfd559baaa3886bcc416e6c7e34c3424979a455e0498ad9a8e05f1854c01b129ba2d99808a0ea0eb36e0a52e100b51971512e94874573be0f863354a15a6d560aa3d400b680e67c205604ed23cf91f160652ccab48a6db11c5970a72c10c72f62ebe649c73047edbd4bda6994e0ddca78b504a47832e670963d4f68ed1802e15529e186a93e2e00cb995fae0bfc3cf5aa08354fff198a0ef7a924051bb750905db17dc48218898d688b921aacf8f25588367502393d48eb6d54c4741f21d030885237355710cf1d5e2b95822a529dc78f0966ac0cfa7e72c753ed324879063a6b67114732e3190c02c160e9b2660677262083cf6d8d3b8e2889e512c0f9550c9d2331f93ce0e0de7daf4e5c0460450706f03dd2b719c12388398b4f4452da4fdd75076b0069a99a54d3cb0755eb054cbe9d26219d385e8bce68d69958fed08b091e004bcb717a34bbdde3e11c4268413223e430ddcfdef3eed4594732fcf583e3ed302d0b071140338dac83ac90913d388eaadfa720d4aa9f37bf0444f8657095d2a1422720c1ea4d6ec4baf11e34f19e2e7c91b402e60d05be2909de08244b34c1911ebb8a8fbc6d8db21d55a7715fe7a8c2d2ab0b9fe0221f6f47df",
+ "0x01f9013701038304ff62830a441794471af8ad882859b9f0545d1f8950ea81ff16b037830751d1b901104300000000d0009e000000000000000000000000000036000000b60000710000000000060000000000000000000000000000f55a000000000b000000000000cb00002d00005000000000cc0000db00000000005b00000000000000008936c70000a4000000000093000000004d0000a200000000e6000000000000001000000000000000000000aac900b20040001f00000000a7000000ba0000009b63001a0056000000000000004200008d0000f1fc00005500f000ac0000b8000000000000002e3bd586a5b900000000000000000000000000003dfb0000000000009c00000000008e00008f00000000000000870061ee00000000000000000000498b0000bd0000000000530000009e9400002f88c0",
+ "0x01f9010d010c8304f0928307410b9499663d687c8879a4f6db52fb5e126552248ac5208302d5abb8e700ee000000430000090f0000cf0000002b0092007f00000000ca00000000000055210000940000000000b2003a0000e100ce0000c05000000000df0000000000005c000000000000000000000000000000000000000000000041000000750075007300110000009304a800710000000059f79000a800cb0000008a00c98700050000840000000000cc0000460000000a000000b80000000000000000000000006700000000000000290000000000a3730000f4ee00000000000000000000000000000000e70000b7000000bb93640000000000000000fab700000000d6000000008f0000000000c0",
+ "0x01f895010c830db28f83043e66946e5e70a4b96ae0d7c21501c115010f5c6c0085038308c267b86fd10097000000ae23000000000065000100000000003500a95b14002e00000000000000ce0012e7008700df4d000000e10000cb0000000000c4005a3411006e000000dc8a00000000005800000000c775200000000000000d000000000026000000090000230000000000000010d800c0"
+ ],
+ "l2ToL1MsgHashes": [
+ "0xe57c5878353ff003304edc9c2adc3c65fd4ec43e13b726271a02534afcdb3031",
+ "0x2b0884fac78637a6cd65127ae15b995f7e1fbeb08fcf580af8a69172b1d83fd9",
+ "0xfc0db6e73d41a7d19b541b2b042a7299befa56c557a8cb2cae5c1906930fff7c",
+ "0x12f3cfe200f7571af89cb2857c2d4c2c6ef0bf4cad80ad1a1fd7f8c66af1ec13",
+ "0x3873d87e1e199c8aa00bab61e0f0f078f1d097f4e2fb70996ff132c60814c546",
+ "0x940272559b668b833316c54d0e7c68851dd7ecf0d9f83b5147e166bcec5c70ad",
+ "0xbda1a3d540ce9314f1e85b67817ec3aed92491d5d3622a4626081491795bc33e",
+ "0xded7c02a7382c117b370c8fb23012bf602a82eb9024a86496380d1b816d3992b"
+ ],
+ "timestamp": 150000024,
+ "rootHash": "0xd0c275eb4e6a7c777360f0c5d942fc0b9678797d799e23f1a76028f54dc74f20",
+ "fromAddresses": "0x5894d7d83216dc93e5655d8ce37c7c386bf4bd2146fb439c5dd0dd7b91d1dce8c558a5ff473d0729a2dd5dd4842c34939fd085c5cd3d3d4bb7440966190583129e86d7fd25bc2cfaaa8502b4f1853feb3d32b0203bc5ee13d45539c8f90f503923f50285997918eb32f1f0d34c95bd45e2a10f8d592af12966e73dba54d4be0d6bdbb81967cc17ec46fc6382f19bdcddd18551bf1b9555fd5d83519fced948b8b07edf19c1bd8fe41158219328d8f7d0e26db4167ebb4571ce178dec144ea028b80d064953aa1cb995ff550c24dc1e7d77bc242460b88a93b347d195",
+ "batchReceptionIndices": [
+ 7
+ ]
+ },
+ {
+ "rlpEncodedTransactions": [
+ "0x01f901510101830321b1830a40da942ea24605b3dd92b9069f78a00f0ecc99fa08561983069b93b9012ace00310000000000000023000000b20000000000000000000000000000005d0000000000fe0e0000000000000000000000000000000000000000ac00000000e200000000ef00df00009f4300ba00004d4f000000000000b90000001c000b00000000009f09008c9e00000000004f000000000000000077000000f8f06b00000000b6004a650000000000000000000000000000000000eec500000000a3000000005200e2000000003b4300d7000000000038000000dd00004ff6266e0095000000e800000000000000e60000000000000000003b000000d4000300000000000000e46c0000090000000000009e000000aa004e00290080000f9b0000ce15000d0000000000000000d3000000790000000000000000560000000000000000000000007500000043000000c0",
+ "0xf9012501830a972c8301613d94a129f53bd63f91b0ade87f278cb941358fd8c752830bdce3b901008b004200000000d5000000000000000000000000f1000000170000004400000000002c510000000000000000002cfe0000000000009f000000002c00000000220089000000eb3d000000004f00001f00d800000000ed000000d800000000000000000000002a00000054003396000000d9270000a800000000000000000000000000370000000000000000005b900000560000f9002edf008d5600860022e6000000002f000000000000003feb00000003c1006e002d00130000080000c100fe00006a510000a30000000053000000bf000e00000000006b0000aa000000f472c17200900000910000e8000000000000004100000000000000000000d0ff0000",
+ "0x01f89d010e830bf98a830b90d99484251ddc66f9fa19490c26439e7325270e46e851830c7a99b8770000000000000000f6000000ef000000000038be000000000000004c00000061000000000000007adf006a00000000000097000049000000000000cc00000000005c00a900009f008c00000078000000000041000000004d00009700001312000054000000090000890000b79f006d8d0000000000fc00c0",
+ "0x02f9015d010e830ea65a830d077283014e0d9452d426c9b7616cefa81cbab33554ac87b0a8d1cf83068884b901320000640000000000ea00000000004d00004400ea0000450000000000be002300ad0000000000bb00b600fe2400007088480000000000180000000000ec00a50000005800004800002c2700af0000000099007e00001a00000065000000fa001200000000004ff6b50200000000009b31000000000000000036008500cf620000005d0000000000000000d120000088f3000000000000000000cd00003200ef7600000000000000000000cdfa650067000000000000580000924260000000000000ff38000000000000cb0000000001f0000000000000fa000000000000000000000026c50000080000000000000036140091000000000076000800e400a5000000846a00e300000000000500009600004200000000607d00000000cea63a0000ce000000d4000000000000005c00000000e3c0",
+ "0x02f8c0010d83032f6e8302f7b8830405fe94cdf4d0b9373ff20b4047c4c0e13eb6de885a28a9830a196cb8960000000000009a000f004e000000b800000000000000000000de0000000000007a000000f500e6ea00000000000000000000b1000000000000a400c4050000000000e64b00004700000000000000000000b4d4000000e60000000000007e002a000000130000000000000000000000aa0000000092000000000039000000f9a800000000000000000000af5a00005a00001b00000000c0",
+ "0x02f892010883057f03830bb3c6830c2af994c711544e6f97b61055985612782b17f7e3853c4d8308b4afb8680000de00000000001a00be00000000000000b791000000ac00000000b800000000ea0000000000f3004a000000d9de003383560000002bda0000000000000000000000000000000000000000a00f0000000000ed0000000000fb0000000000320000000051000000c0",
+ "0x01f8e901028307db24830c7c88945d35dda59aa3c9a260b9a370ea63f3811b14813283057796b8c36700000057000000000000cc000000001f6c002600000000000000000000670000000000cd79b300004b2800400000eee2000000da00007900000000000000007e0000bb0000000000000000000000003e9200d90000000000000000000000e8000000f00000390000ab10000021001bc517000000000000003758000000000000b6004d000000000000000000ca000000000d00004be0000000a600000000005800000000acca0000000000000000000000000000000000000000000000000000d800c0",
+ "0x01f90287010a820122830c6c3894c499a572640b64ea1c8c194c43bc3e19940719dc80b90264f4b476e1000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000110ec440d22b2ca1067c0dd36d803a04f625dbe04050b50b5a1a4d5427c2887e7285e9408bccabedbab27c76d2bb2d3e0cffa6af1ebbac5aec871aedc842ca6b390495eac22e95cbb4ab0bce2485977c8fa6019a4ec4e923fa61db9c34e8f99ff1c742f7794b5d71717bd55d5c5f0d8cc0d20b1b0af506794443e5c3e7f880dee199c28c50e9cc84aef76f6859f5da2794461decf77546a9e927d6f1c61d0fb4e7442d16e781c604d29f1f3dba7e1d69c03eabb5ea82f7089daf0093dcb7d21a2c98ce7bcec12484ab946138567b737ef4304fb7d96aaf47745d719e14de9fc0b684157eb26dec16775a3ebb809e4d129a96f954c1d468c2cf54be001f41232f88425a4f75e6ddd8f021c398579409d295b79009ea9458e68a3c21f8a3f2407eb1cd5d40c476417abde6701abea68f8ab62ba5cacd535af956808f50137e397c6e105bdf4d71e85d420692eb20f116d92dcc258f58a07d430f2b45a5d6d10599ab963c2013830299a3e60ec938e710cccf30879b926003e2a48ef137136db59e894a5c2d1fe8c5231e2b14253eddb4bde0ff9a394d5702403161ff60c64b084baad6a67b058af255b27bd75d962d18c09e4cb08a9143d40565685b26cb9c2f2ba279f05d1ef648d6efe6ef90f843a3777dc3e63fce3df9c9b2160eec8ca811aeee1d7e866ff3018f4960830e40eb9aaedd7f408df0d8d57b6a39ae52db32edb0de77d1293a02b67efda3f32c4eb3c08c4d4a47c5c59a9a6ef19c200943aafccecbc0fed76814957edf99e5456c5b0304840272052c65",
+ "0xf87f0c830d774b82cd789487af298a085826378c2bf549e1d103395f726088830cf121b85c0000840000000000730000000000aec6b5006b000016000000000e90005a0b0000000036000000000000000000001f70000000005f0000000000000003cd00000000c400000000000000000000000000a3000000002800005d000000",
+ "0xf86504830a525f822cb5945ecf95a2abea7d8ad8a5d9af089e6136dc2fee1f8305533eb8420000f7000000b90000000000f8000056f4004000fe0013000000000000de00000000eb00000000000000000000250b73000000009f0054000f000000000000000000",
+ "0x02f9015b01038303e01c830f245a830e41bd948537eca699ced809c6394ad33e649d780bb61f3683041bf5b9013000000000004400000000005800000094bf000000e5f100000000d600000000001e003500c80000000000003d0000000000004b58000000000000008c00000000000085000000000046b04900004e00006a00ec00287b000000009d000000000000732da100bf00e4590000000059a7696c00f456d00000794c000000000000000000000000000000f31100f00000000000b5000000000d0000000093000000009800000000000000000000004e0000000000006c0000140073bb000000270a00000000f100007d00000000009c00a80000007f0000a9002000000000da0000b929000078006500c00000250000006a0000000000f00000000000000000000000650000000000074b0000ae0000000000000000cf0000001a008300000000380020000000c30000000000000000158d5cc0"
+ ],
+ "l2ToL1MsgHashes": [
+ "0xb0c36c4c14c1f8c61574f9f1033861f799964b2a0327f032fd25202f399e4198",
+ "0x9d62913323e78c58c3712067e246253101b8b9dd46a3cbe3c761f3daf1499ade",
+ "0x8b0dc7a16e44b42d312ba8a71293235efa978397160cb1458330dcfe92e07539",
+ "0x251dab29f567462687344b46ffedbe8713f70db70ef1a620efd5060599f9fb0a",
+ "0xfff3a1dfc3a8f2e103469c187f327b5cdb72de6aa5645f6208f4721138729467",
+ "0x88932e900cb387b32c58516a1353654b67753423ce4f4b25bde547265f5d8f1e",
+ "0xc639a45c43201c06c497f86eb53dc769f1aa31d90cb58b429bad95eb26fb0826",
+ "0x1ac500979280e94ff69e503efaf3f24a75affd70006a78f93d83ebfcc0dc01f3",
+ "0xedcee120fc2fc8b47051913966c7f2bb11436fee6f874337f29e0cfbe6dca712",
+ "0x2bf1a72779b8b3660eb512213f24ab3e554060632acb915c806b2a51a45db02f",
+ "0x7ea4c4d02305e8f7025fc26d1f845451c57eefcc8722464e7629a9498edbb942",
+ "0x5808bea3b1b4979d24ce64ef8ea10d136817b2aba2706410129300380bd7c257",
+ "0x5f94536bb90732a224d6ac38a27c6f2d52a600622badab95273db612c0d51cd3"
+ ],
+ "timestamp": 150000046,
+ "rootHash": "0x1d02c12fae15605ecebf480cb6aee54f558871069afb056792df6ab5490376e9",
+ "fromAddresses": "0x9d4841b372ce799ae6c0b342a19231fc972915deeb07f8430e0a1d9c852854c2260525f7b749b32af95e1894e441a81f99a88f3cc54c4cd65b96db7c6d35f29524fa4d6760b8fecf6c1a105f10874210654698556558dd3ef9af949ef6286c00f31ebdf774e775a96cae7bc441e57a7bdf5c2446d91387c6ab5a5a7c1870ef1d333d8445b636f4b67411b35d1d49a3775d6e7c8185fc36f23f7b304774cbe1a9eacb998c89ce0ff94026d0e469f366c9dde60ed485c54905744d37f406ec561bfcfb24f81c5815b32a79792ba4cae063494aaf02a843a0ce9c4c9a4b",
+ "batchReceptionIndices": [
+ 7
+ ]
+ },
+ {
+ "rlpEncodedTransactions": [
+ "0x02f9011101098303f3fc8302fbba8304f4fa941111aaf8811fc83b4267c34051e0e02e24cb187f8302a241b8e751440000ea0000000000000000000000000000ea0036120000000000a200c60000000000000000000000000000cc00000000000000000000d800000000880000000000ecf60000e3000000bb000000000000850070c4000000030000000000009e0000000000000000ee00009300008d00000022450d59b494000000000000000000000000ce000000000000000000ac220000ef00000000d800000000001a00003b0000000000eb007000007a000000008e4900000000000000000000000000000000000000002e00000900003b006200ac0000e50000000000000000510000005a00006f0000c0",
+ "0x02f9015201058309567c83070d4d83043db694e0e92520ae3901f616bc8c02507e0f7fb1f6954183081b8bb901270078001a5b00d4005f4b0060009d000000007a3800ec000000625e0094000000000000000000f9c3000000006b00009800000000b700000038000000008000000000060500007000796e0000320000c700960000000000e100000000005ff800cc0000ae000000e200000000000000149a0061000000000000000000000000000000007b0000442800ab00000087000000000017000000000018000000210000cda900000000000000d44700002e0014000000008049000000000000fc0000000000b700003300be008a0000000000bf00000000000000db0004fddab62b0005589200ef0000000000000000000000000000000000000000000500c400630000850000de00008a0000009a00000000004e0000000000eb000000006e0000c3b1004f000037f700c0",
+ "0xf8770c8303dee983014d8694f662e8528737891dde58583db48eb9b7032ae7e483033fc0b853a20000000000000081126f00000000f0000000000000500000000000380000c43a000017000000e78fa70000007d00e1000000000061520000004500000000000b1fc0000000dc000000e6bc43000000007200",
+ "0x01f9013c010783066bcc830d1ac194ad3226731578059e8f1b12259d0e9c93c8c8321a83067b25b9011500000000000000270000000000df0000710000780029007c000000b87b0000000000000000b70000005fc76a000000000000000000000000d2e00000000000000000f9fe0000000000001000000000005c00d68600000051003b7800000000000091000000c000af00000000000000009700f20000007d0000003a0000000000000000000000000000af000000021b001200001300005b00d9950e00000000000000309a000000480081be000000000000000000000000bb00006b000088e300000000a900000000000200003796004d0000002400b60000000000000000007a0059002044000000006b0000ce000052000000000000000000000000be0000000400005b004897000000000000008d000000000000c0",
+ "0x01f8530102821af2830c0e61948e809fc3cd3cc653190cf356fc73078a30e872ee830debb0af007867380000c000a00000000000000000dcfc8900000000000000bd0000000000000000390cce000000c9b8000005c0",
+ "0x01f90287010c8203248308edc094c499a572640b64ea1c8c194c43bc3e19940719dc80b90264f4b476e1000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000119e54481c9d18a13656ef1cbc928251f82c131a993a3daec9d57125069dfab3b0134624e9138915e0c66348e3166eec468ce6889e07ece82112f70f8faafc09a0368ca86425d1b3be3be03483a8b90e09d4f14a3aac62f50d74ef3139d87a9491459266bf3b1644ac584baa90844943ea670fb77ccb72aaec013450efdb3f7e02cea0819cf372aead56f33c2572606cc8a518ec33b4db544bd50206edb2ed124735bb756b2aa107d55ff8c5be61f8d67f9fa61d136a9e20f824346d0630a41da3703096d730de1c3299033525ff4da4627ba51ea546a6fc4368605cc330f022cee8a3eab4808d631e910a7b1224b045af6312f8e16b6496e209d016c8348b8f293649c971b5039fbbf5fe96699eca0625b67c3866c743f81c98f61a50c7a6519197d2c0e328a969e8db60023e8dc02328e8e0df276791d198bc9fc57414dc1a4318bf37fbdd676066cadd89b6dd8d828f0f95aa02b3d3db2999e78bca2fbc519adf415f25dc2857742c644b90de9348ef83ce205aa61ae95d485f107086ed9d5c75b7f2bfec7542e735db4f22ef74ec973bcd07e1610f66b95c70454651c2d5e1c0220eb3f987055e87798ee1ed282cee707f65120cb3cb87612b6f89ec75d5bf9752b044c87daa80625d58a55b8143c277bdcfec37e0c11a1447965dee8a798930d789ee721aee2d2683d0e11b8330069aa6223085d4ceda1bf678d93a557a7e56a52a6eda9b2f6e9ab2dd28a10471f46863e1120f10440e51a43f05c627c285c05d9e04d9a439307be4ea07aa2fd38082e29382f0",
+ "0xf878078306c70983013d2c94ce39f873eb28503f8418e48c732b2e04d7c5c2fd821c3fb85500120000ca005700000000000000000000e0000000000094060000000000eadcd30000e60000300000000000008100000e00000000230000000000000000000000000000006d23003ea40000c40000008500004e00",
+ "0xf8c2088304e84b829d9e94c4beaf6bfa068511fad9d8ba967420e34733d021830d7cd7b89f5e00e911000000000083660000470000009700003b0000000000000000c4000000000000a50000c600001200001800000f0000004000970000007d009500000000920000000000000014b6000000b40000000000008de300000000000000000000000000db5900000000000000299800000000316b0000000081007cd8001600d30000000000ca8e0000000000006e0000005f0000000000000000000a0700",
+ "0xf9014a0283055459828aae9498e54856b72706c20df5afed2a287c16c2b393ea83039bafb90126000030000072d69655008d00000073a30000b2008000000000000000000000000000860000a60039000000002d00ba0061a300000500000048000000010000eeb0000047000000004c36a8fd00b05200000000004c0037000000538c00000000000000000000b5000000000000004800004ae00000000000000000000000000000004c0000008908000178000000007acff8000000000000e4b100000000000000009a0000785500050000000000000000000000000084373f000000008a0000160000da00d17f000000000000000030000000000000000000000000a90000000000000000230000000000000021001c00000000000074002d000000000000000000000000cc000000d37d00000067fd000033004700a7000000002b000000c4008000000000",
+ "0x02f90148010c8305a1aa823e85830c6b4094daa22d985b44c85ea0b3aa373aa3feb7b12a88a0830ddbd8b9011e0000000000003f00820000000000000000bd0000be000076000000a2008600000000000000000900dc000000009400000000006a000000de0000000000000000cd0000e400000000000000000000000000008c0000795800000000f93f006745009300cc0000026bb600000000003e00004f000000000000000029000000e70092000000260090007a000000000000000000000000000000000033000000ba0000000000000000d50000000000c80000060000ec92a0000000000000006b25001a000068000000000000d400000c0000000000001b009270d9000000f8006dbc00000000000000d4000000e30000001ab132000090000000002362003471000000000000a50000004b2300000000610000005c002a00000000000000acacc0",
+ "0xf8f409830f048483010a629464ba9c907cddc470c4c3d6aa46c1edd0a494715583030df4b8d0006a0000000000000000000000006c00b30000007d000000000000000000000200e80000002e00009c003d00cc0000adcdf3009e0006000400000000000000004000000000000000002600000000df00d873000000009a007d00000000000000dd006c00000015770000000323000000002d00000000000000000000000076000000720000000000b800000094bc004900000097100000000000dc000071006b0000000000a3000000cf2066000077000000007a00000000000000590000574d00000000257e0000005cdd00000000de"
+ ],
+ "l2ToL1MsgHashes": [
+ "0xe13b4e3efa0b75aa3b01784739da177af6569e7763dd410473bb35cb7d583978",
+ "0xbdef1a105ade2e49f84c06a41e8aad32d743fed6c73410800db5c5bf8b925d3e",
+ "0x0123eb6ce1e77477907481b6cb3ba9918c32a95fb0480f7c38e4fcea0dec8c0b",
+ "0x08993ebf157a94d02d36fb315e1cdf83797a9014c49da8abfd902b0d095f87db",
+ "0xcbe3d53e5d16770ea7b1860ca9e0a4812c6f5e76081b58e94d5731455fc0b438",
+ "0x92f84de73d13228f943c328295fdae396848bb40041de1f86eb789c149e17e45",
+ "0xcdd876812666fca2abac1ebd74f23976d147bf64829117adc4216ba1fda233c2",
+ "0xe70ae52f6c5dbbac485e2df10fc573ce11a38cd6ef8edbd18abfbe1a9a1319bc",
+ "0xc5e16c90f519b236deeb61e72acce756d424b0675459e1a3db892f3571efa8de",
+ "0x0dfdf2faea51bb184c046d14d167df1b69da3cdcf27a03eee5de8da6e797ab14",
+ "0xa7a23554a3f8c9dd53df00a20ce2aeb915309329a6b192124d2e408df6365606",
+ "0x0ebd56ca85079aca10e1bf1bf294d0df4fe51b12d650e5889048fa125469c2c4",
+ "0xbaaf221b433e32170f67a23a7056d69b763a4e999129f417259c1c8571809a48",
+ "0xec30ade3c88cc14c0d72f11df48c2440b078a304983d29f08e21722b2a2363bc",
+ "0xfa6967d53fce7f8eff8f712dfafb51f9cda80b0e9222c7949c0232fd30f6738c",
+ "0x39f15e308dd8060317b18e95e77b20e7f6c6cb880782e099bb2f10322bf78038"
+ ],
+ "timestamp": 150000049,
+ "rootHash": "0x11c632ae819c01fa4273277342a1e4abe6ce0e49ce4b071da695d47ffb4a0a45",
+ "fromAddresses": "0xe13bb3e398c33efc62a47456b60d7f012ffeb554c637695243ef98d33ea1b1a8f6fe1039cbed8f5617e726b753d0380cb4039d75d32c8e2cc9012f744e57b0adcf58b1d8f4fc5bce38cda1583ce473b836c5fc367883f97e9e44f682bc5173fb56581546180c25bea173d6e2d42fc779da22e3f58f2e25935a16937fe8f59b0dda2b51d20fb0d201347533dff9a3c8795fa1637a8f163b535fd327512e71c72859a2f36a835475d9b0b206a6a1cf5df50ce37a4d7bd104d2de90c99cafa0dbeda01fd36a9e013c2a219a2db04fe432b9042f9a547cef0e4f3e1935d3",
+ "batchReceptionIndices": [
+ 5
+ ]
+ }
+ ],
+ "parentStateRootHash": "0x0af1a2b824be146c558a3b0d8a5f04a80e9a097ff85ac1a1b9fbe7c860de87f8",
+ "proverVersion": "0.0.1",
+ "firstBlockNumber": 1,
+ "DebugData": {
+ "blocks": [
+ {
+ "txHashes": [
+ "0x4631fcfe46fb05f3940eee9b27c971847e83ff684e493b760352f87c267ef4a3",
+ "0x5d1dbd696fc6d7356cbf8c5dd11266a10d7a5803d49eeba59d8eded5d6878097",
+ "0xb674880967e1ab1f39296dd4a2c6bca370aa120e7a30491e233291f403788f77",
+ "0x4bd9c20d2e49d97593c213f65ba293640bfa5ad63bf4f7eac7ff4e19914abd59",
+ "0x4194a1f05568fe60105e63a897e27c30bae0bbe3b79bbcd72253657b33ef957a",
+ "0x7fcd35cac504c334a2e9d5eca40a751fb3f1c7372f3ba8579942a271eb72a7a9",
+ "0x44532538d4ab7a1132fe0cb3cfb90d831182f23f13db917d514a02a51161870f",
+ "0x609f40737ff13c51b7e52f471da877332010bc7d929673f5b1bd7baab854932b",
+ "0x5d6f1600d10e11730f70b37c9de1f29f776815d1e05e9dcfeab0cc9a82202320",
+ "0xfd9c9c53bb387d0a965d2e297ab72026a20afd9e5bfa2be4201acbfabb532914",
+ "0x1a037bef5feb12c0e3456eb2287c8701787289e7e43ff862398eac76b05e42f2"
+ ],
+ "hashOfTxHashes": "0x0319175c8bf8ee33d4295769b07522bacbd6b9032d05e8ddce6a8287d1f166c5",
+ "hashOfLogHashes": "0x552a627ef7aaa083396c2576333dc0f85ce64d9b1bb0b41a7ee02682dfa4cc63",
+ "hashOfPositions": "0x405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace",
+ "hashOfFromAddresses": "0x27c8152d1e3a2f7e95088ec5defb2f6e15436a69297e64c53a3e3c433535e665",
+ "HashForBlock": "0xd0c6fb5059bd5c82eb938690df288319d3a055bd00c55b2429cf07b6240df2ed"
+ },
+ {
+ "txHashes": [
+ "0x9e7ff22620016029f58e528edebbeb9faec1a0adda45218ed29becf248fd699c",
+ "0xc508cee9b97302451e7e7fa2efcefcaa335040f5649e3ffb980c91a34248cfa5",
+ "0x58eb80b67e528c9c3c80fcba46721708d170a303adfa5c3d3b1a07ba14e40c61",
+ "0xf316c675feac4aa25cd64cd0239c987fc83d2d02b3b8309368050b475f2ff15d",
+ "0x655e21c5d3a991f2fbdb9fcb5420b369adba24afb6cdb5bb92a291494355a8b1",
+ "0x58a105184e9be867550b49ca5039195b4709a39ab947022b23639cfc8dff0bfd",
+ "0x706e53ca8a36f77abfc975c9bd0d6074a83a715901d538bb7b45283296ee9261",
+ "0x1e70d57d10b9004d6fe6f2fc3910b182ad85f1493c260d33754dfd33e1a60c4d",
+ "0xc06696ffffb236346a4ea4a7cfc7dfe984f950a666c1119629907778cd178525",
+ "0x685d27dd9d5bec106e4d49e5d077fd6713685208efe651852d0cf5fdb17c73bf",
+ "0xe751be2d2c1e70566e89db8ceb7f024010cf64fb75257cc62d9b063623403aac"
+ ],
+ "hashOfTxHashes": "0xea4c138495134cc47e58d3e976848a8ec7e4ed8105c6db43619978f8e0336aa1",
+ "hashOfLogHashes": "0x2b52ecf47e62171ef33051f8776b04635b8e7f653e4210023705720841d09b52",
+ "hashOfPositions": "0xa66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c688",
+ "hashOfFromAddresses": "0x5fdb5ba628e53b2ae8b1bac57b5e0828ecc0f5bf43421ef6fc421bf53420b39d",
+ "HashForBlock": "0xad47ef74879c6397ec3baad796ff15aa23b92654247c12884b59a4613165ed91"
+ },
+ {
+ "txHashes": [
+ "0xcf4ec7b20f1c07a73e2e632c956ef7ad81c914ea8290c60c3b81a468305c2798",
+ "0xd959146fd31d18be2ae3612e865e3b0ba57c85a34cd213a00aae9dc52c17a9d3",
+ "0xac9989e6e2be68a9b3883a263dfbbce479d32407c72914c144440afe3b3d8096",
+ "0x383592c2031d66328b30000394cd5828ceaa9d11fb467eb0082e6b3d579df969",
+ "0x331776e07dcc627ecb785d54ffd905f641e800cc2c3dec6b5107c900876c502e",
+ "0x61d98430c69b5a1cf6524b63f09cefc3bdfa3a9d26061134662f7ddf48885cf5",
+ "0x745b5fb65d8ffcf1404fc11762a113fdc5fe0be5df3e6d445a43fe14141decb5",
+ "0x2fa3ab47428492e2fbb9685721af1bac63252247dfa255801a588b2707650f32",
+ "0x84762f1056e985ca3566f1997a4ec2e993de8d5b2f0b1309444411870626c940",
+ "0xab248f4ecf07488de545db6c4e7962679913457024a4c36cfcde3b9660487203",
+ "0xb8c97d91cf95ec9d62e311d44de1d5cab6e3297a8e48c7de33a5609c5275fa6c"
+ ],
+ "hashOfTxHashes": "0x658cb158ebc0205cdae95b2daa6f31c7ca59253023a7063027a47a8e065e689f",
+ "hashOfLogHashes": "0x728697daa10aa03a8aec3ebc114b3304f1c5db5653c84a79d4104e062fd962c2",
+ "hashOfPositions": "0xa66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c688",
+ "hashOfFromAddresses": "0xfa8da1c7caf5d42b35f1533a9c4bb0f0bdc9d187095d777ee4c897c13460fcde",
+ "HashForBlock": "0x7dd48f72dcac14922f86782ab62fbd60d9042e044d70c1e9da478724a16cb4bf"
+ },
+ {
+ "txHashes": [
+ "0x1ffc24142c023f4cb6ce78a2db5866847c2d1afbe76ee88db20ff036ccfecb7a",
+ "0x693af87c7dbf5692bc902852201e2ff11ba4701932a994c439cfaadf3a3ac300",
+ "0x88772f56a578e2941b4965f133c6e501958c0e1ab2071665c5c3cb2cb121df46",
+ "0xe41a41bb1fe6badfec1a9fe00ad2285a8b54f8f8eacad2488c7d03885feee7a1",
+ "0x138ba32906cc02acb22c02b2b3fc49112cd1ef26ae2df00ca86f4fcd8a68f68f",
+ "0x56d976c7f0f2030eb4d91630b0edcacf08c3b1ffb02a5e04a59cad2ef72b68bd",
+ "0x4f8920975f81b0fe7ffa143e1704e20630d054dbd8588f0111a2f787eee2679f",
+ "0x860e9ff5c069f1c53893c18f18dfae51a0395687306e73bc49e098228b695cfa",
+ "0x7f66826c84220abea0cb633a54b8abc7c083147c1eb207271c41a205773810a8",
+ "0x3b51f3ba5c0d3655a1de1a5c6dfbad22eda62d9f42b026a8332e2cf8d5d3adf8",
+ "0x1267e1971ce31077fa25f880c2c3a872de5005478e1f33083428bdee9727d006"
+ ],
+ "hashOfTxHashes": "0x93800650fd3d07a58ffc1e343c3cd296d854ac457a981c2e689173d4c1399b36",
+ "hashOfLogHashes": "0xcbf233e14e75219f79372973c4fa4d400319d9e1994f1c8f979b32a643ae5fb4",
+ "hashOfPositions": "0x036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db0",
+ "hashOfFromAddresses": "0xa616fd0e05e41c9655726b303acdb9ae47a6bb6f81e1b9b00ce80aa34eb938e9",
+ "HashForBlock": "0xc1b38d671c834a566227d29f4b259de431a1c56c6cfb846cbda25b521d30193b"
+ }
+ ],
+ "hashForAllBlocks": "0xa6b658528cc1d303c7f3c96fe561e4363e0ea7fa8c2e3322d6d9e8652928ff60",
+ "hashOfRootHashes": "0x2237887ddffbf266ab1d1f3c28e19bb2c8ede77f394549ec826ef13a0f8d8fa6",
+ "timestampHashes": "0xb783d7024aa2ab96355289ca397e29356bf7a815ae55f79487a40eaa29125a1c",
+ "finalHash": "0x0e06c1ac6c1c41f193e5db17a0c2af992544e5d4465d6b87951c885c92707244"
+ }
+}
\ No newline at end of file
diff --git a/contracts/test/testData/Light/rollup-2.json b/contracts/test/testData/Light/rollup-2.json
new file mode 100644
index 000000000..6f6243e32
--- /dev/null
+++ b/contracts/test/testData/Light/rollup-2.json
@@ -0,0 +1,216 @@
+{
+ "proof": "0x09762ee2987910744a9470377cea595498da6d09e64582ec23c8e8966d5ce720114b7018889cb6e9766249dc42251ac5f5019459e793443800bd5ef85c486c7718fbfc73cfa5ebce15c748dece01b067768dac040367e6ee7397d73654746ae115275d17172ebbf8f2f9c116cb5c084078ab78f8b57648376762b049b17d808e202a407a85364987182a261862439d9c0f238ea9ae7dcc0d57b8de12ec9cf1e42d9beed070898e259220f6e8f524f53b30bbe3db185ebfe44bec9e7a1a6a42700e10abd53d0426a2a095b0dcc2a02fb86ba0373cd12b456757876d683067a9032ab3aaf8c4612e4cd5d76b59fb2e6039bf35d99f1b63dd05b2b5f7f27dff22ce1a3ead9f714f8f602f12caeac2306f6627cc831e93f77ea7d29a32b1121d9c3a05ec7bca8d0ac2b84e1d62089c6398eb5bbf855264a7c27f4e3ba46a1baa5e8019691d70a04cdb6e65312da20c8976836e0e65f76f998cd470c8750ee530686207c3902bf48c5891f875f84ccee7a79877fad7cc1888e1ee2d016052dff83b0f2303a5831728a77539cea504bc3de28679a5bba76735b2520e0843446825d6851759efe6c4df60e8aac7cc89de45ae5535610b83ece526415e476fe33fc797070d104c101129126069e63c6c917f4c62533580f962cd1cc212cf00048334415f1d06c05d05bffade92d6093d8814cfb1497cae63d33c9763e0705f36a4546a382c53d67003a3791623f1b2aa07583f26e68861f021e3ff7d3c626c8092cdd6b7192a1acdc92798dd00c6cc12afe79330d00ec0e5f18c6082d424008c4a894fd007ea4a7754202d93fe49032e0cccce1a395cf94a3189d9a4e2b912bee399b2812ed49327f68e2a3326397a29ca31effb60ce7586cee67305f8b1b6a64d1e889a17428e1ec02a06c505473e5899430f981fd8c9cd84e6273cad111807e1c34a3f207f46ae6da7488b3e2aaef9a4a25d7ad47170c443d5a49c45ea5cb0ec4ba54e19619702d9bfb42f8a97d952341a6f2671db4afac7c1c7fa7766ada09fb5d2c32f4588b3e6567ae8132fe2625c0ba9d3e414d58b7603f53dcfce4e585d7fea93280a3ae85ab540ba163865df5b60ce9e86a2ab5b000e279a5e5c2355a2adef9706c5ecdfa0384c35f06fe411a712c98924483b2a3da506f6adbb5b4a088b0eee1eb3a129e6fb6317dad9c4fd2491f039d4e0d154db3d14dbdd39f62c96acc2f611ec38129f1b1c468d3798cf4c24e66ed2ea0494ea92ad73036436735e6639700f078b436c89a1b8bf1901b7e7e6f77a2f1b3cfa25a4b3bd07be03d62a1a70ff",
+ "proverMode": "Full",
+ "blocksData": [
+ {
+ "rlpEncodedTransactions": [
+ "0x02f8450102830c1f0c830cb918830cb26a945c5b9d6852e5c60a1973e571e589801f85fec82c830d08b19c00d00000008500001700000000000000000000000000000000000000c0",
+ "0xf9014c0c830b315882140594abe44215e66392f0152aa220e106178e77cf9ee3830a71a9b9012800000000dc0000000000000049000000000000ab020000002800b5000000002a000000000000000000004400c9000000200000000084d89f00bd0000000000000000007a003f00400000000000000000790000b8000049000097355d00005e006400009e0080005a0000000000000000003800000000000000000000d90000000000000000a80035000018000000006500940000000000000061000000000064173300002a0000000000db000000021e00490000000000000000000000710000005d4b590000000000008d0000640000bf000000000000addb00000036da000000000000000000000000580000000000000000b4af530000000000000000000000007800bf0000a666f40000000000af7b00002100ef0000e91a0e006200fb09000000d15f002300",
+ "0x01f9011a0180830ada50830b5d759464ee2aeb300e1f5ba574b535e79548dd708b9144830ef514b8f46e0000000000000000000000df00000000007600000000730000006ba6000000f100004000c7000000000000008c00005b0000db00a8e400000000007f000000000000220c004e000000000000000000000000002d00000000000000000000000000000000000038e50000001200000f000000e9be006eb200000000000000c4005bac850050000000007100aa00fd00bd00008e3bb10000000050d000005700e3000031000002130000006c00000000000000000000000000e8000000000000db659e0000000000001c2b0021000000007300d77e17000000a50c0b6cf6000000ef0000f100002200000000960000003b006800c0",
+ "0x02f8ce010d8301118383010a96830cc128940a49c19e08445ca0e96941a35bfc9629eb1a679183035228b8a4000000004100000000d80000000ff2000000f300f00000000000000000008600000000000000000000ae000000000000000000000000000000000000000000310000000000c1000000e900d700000000ca003e53006b000000009c5800000000cd0e0000fdd60000000000000000000000000000f70000cc0088170000d900000000009200000000000000c20000003d000090000000d185560000fe0000000000000000c0",
+ "0x02f70106830e6e7c83096d458307b53894ab11c6a49cc5fcd1a8574643ebcfd51d9a994a72830a7ddd8e5400d0700078de000000c6000000c0",
+ "0x01f8fd010e830de65f830c51f7947dc5a75f25cf09a0ae6e17f878a9734c2c6726b08301dd25b8d700000000b5000006000100000000000000003300000000e1000000000000080000e66200000000000024310000fb0000000000d60800008600001d0000bb5b00000000000000009c8700e000009700d800000041000096000000e100006a0096000000000000b70000000000000001000000000000004d5d000000005500f5009300080000c7300000000014000000630000ea000000ff61000000000000d000d8d9bf0000003c00360000000000000000000066f315006400a70000000000c300000000000000290000000000000000000052b000e100c0",
+ "0x02f3010a830f22de830239c38306fc4394f6b10eb53fe7665ff3fbbbe3ee9c67f47b24fca5830324ce8a00000000003200000071c0",
+ "0x02f90132010c8306721683079761830b09c894647dd3de81b711b89eb0efc55f0c4fdf8f3418868305b32cb90107000000c800cb0000000000000000000c7a2c0000000000000000000000000000f200bd0000000000000000000e007b000000000000f50000002800000000000000003e00c000003e0000000029000000f5110000000000000000000000000000005f000000000000007974005c000072e400080000470000000000ad000c000000000063000000000000000000171d000024003600002e00000000460025b200000000000a12000040f3000000000000ce000000000004ced000000000000000007b00e70000860000000000000000950800007b000000000000006945000000001f2900000000000000006f000000c10000004000005211000000acc80000002abbf1ab000000c0",
+ "0x01f8ca0107830ab65e8303e5af943019bc5ea53a06b92dee028205911c6e795dc90183055987b8a400b9000000aa07860000000036000000000000b07247a6fd00700000000000d90000c1000000e80000000000000033e2000000000000000000fa00ef00b00000f700004500000000000003000000000000269ac8000000000000002900c600000020000000330000c50045006100000000000000000000f20000007300000017bd5d004901000000d6dd80b17e00000000000000c40000a400002b000000000000115600c0",
+ "0x02f8dc010b830d910d8303df3582d5e49420b8b9d1174042ade6aa05a61aa7f18638b2748c830969c8b8b3001900006b00f70000000000000000000000006335a40000000000bf000000002900000019000000000000007a0000da00000000001900000000000000f7a700a20000b800000092005700000000007b00000000008700e70000006000000000000000660000000000000000bb0b00930000000000d90000db0000000000004a00000000000000000000000008000032001800000000000000000a0072800000000000006b000000a900000000000000004e00c0",
+ "0x01f9028701808201a183095c5594c499a572640b64ea1c8c194c43bc3e19940719dc80b90264f4b476e1000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000116bcfaf2aed48792e452259a176ec5da8b19493561ba48fdd2bc94c17a86574a49f393fb8833ca4762744d702d62d7034ce853249c959c7293c6d4d7ac45f562deba06353e3a5b2fb49963c3182cedcf89438f0f5637a96b4ed732a387dec2fab2600350da8685f5d10e3326d9dd1bba716bf4145fd07f385dba1a36548b67f1999d6dbf3cec51b6f24565df3a4a16e350d001a749633d461b227067bf4ab0f756775ae61fd364ea24cfbce13cd14bc2e4399074cd041c1999e0b1b206aeb4ac7e50c62ef3c7d12fabdf95482b6e8b96fbc53e797a490c66dc9c9e9db27b403e97cfb1adf4288536994fe6e59d977394f0a5bc3923ca699fc196a79707c1e9426c95d9187dcc4221aef1b9250cb852f878bfffc0cede72c7f908de9f4e69da9d165228608c332664c82506fec6bc625b474838b16eac426d16005c88ca057bbd684c63d8fcb9ccb913412e3c8f82bb0a7adf40ea3ee053c894b5ea01947ba0e2008890024117362f8afac92a852972cbea3ab9f68769caf34748001f0d8f90ec511cf44532acf001570208a9f290d70c3b8eceba8667ffb0cff1caeccfd3afe7339f54332b4dcc0472d13855d48654b14e474291d6b218cc6324cf81df3d11a875ebfb3840a498208d0a2448d6ff63cfbcdb551697d85c8dfbba7679d4b125c12b182280f262278fd82067a8b9411301b83e236fa531e2e5a411b382c0ae27fbdd7abdf7d6cb72b4f1c212ddc6524f6fc61896137678a9d786bf92123bf63a5fec0426a12e79ab8f0831e75caa7bc3f12205f72237a"
+ ],
+ "l2ToL1MsgHashes": [],
+ "timestamp": 150000118,
+ "rootHash": "0x34a9f603f233c2f13047d698f089efd33de3154530394ad4f3f82c589aceb321",
+ "fromAddresses": "0x2387b5397c53e2a09eeb35b46f457bfff57174c9664c09dd43d07590612eaa1b90db64052a95ef1122e38e582daa287f101f587495d18d8c30a8eb315f728e5e3b5ea11d76b8d7d3585131fad267a3c4d1fbbd91e371989fc2aeb032b64a101bd15b141d84e327efa3018729f3d8140f6302d48ab376f43e82aced053d1cd1261ed37a99b665fd91587656651aede1f18938853918dad78a5111e110dc94148bd022001340b06d62095b6bff02488c8ec3647c05a38bef4621a338600bd21abfef8cff8d46d2f65591dbf5eb7379e6d51fdddae9f9c4618585d191fb",
+ "batchReceptionIndices": [
+ 10
+ ]
+ },
+ {
+ "rlpEncodedTransactions": [
+ "0x01f8ef010d830b4f92830ba19094cc4c096629f3b50e8eb8a50d3652f05ea95c03e6830e13e4b8c900000000006c000000004d00004b003f2f00beb400e40000000000550000e500b600000000a3000000000000ca00007e000000dadb000000000000000000f300000000000000000000cb00001bab4b000000001900000000000000000001a0be0000c60096a0ab000000009e0a0000000f000000000000003ec1776c0005008c000000ec000000002e0000492a9300008b000000002a000000000000000000eb000000d8000000dd00f5000000000000002dbc00000000000000110087000000001981d50000000000c0",
+ "0x02f8a70107830bb9db83069f758303bc6594587c784a338e12c5cbb1b433015b5d3f984f19ba830532b8b87d00b90061000000005e00000010033d2a00bf00000000c500000000000000000000000000000000a60000000000ea0000440000b800000000004240c000009e000000000000f163000062000000e7004d0048c90000000000000000000000f5e100000000006b0000000000dc007300000000003100f1001700005d0000c0",
+ "0xf90285088201dd830af73b94c499a572640b64ea1c8c194c43bc3e19940719dc80b90264f4b476e100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000011c144248eaa3df540961296b6c1e9d0bdec947aa49373d9cccde539dc47d25156defa2eecb66b20903147ae394526c5fe815babcaabe10503ac85bf21aa8fca326ece81e60ed6703c148fdbe92150da174017caad6bd1f90057ffd06baa1ce01c2bbfc2f3a9dec6e3b4c86bed8ce1988ebc8285344e752ff0ddd2c1a55de80f4fde7472a804bf4b4b5b56cef8560ba4aa42574f235037213ccb06ed7bd8088711245df29016ed0bd7c0c148653995cb836a48f6bffce86bce570b26c303aceb55ac66e971081009ef09c985468a78bc08aac2df5b694837168e782314edd9f0da2a670da1ba934299ab5e0e0f88cf3fbfa4a306ae2bb71f880ae142d9b58393713370ea92decd17a370dc6edb182e01fa9da80d5b4ea1d183e2d6975d363aec77ec4d67f3092ae02783b852d1f82739e1ad3551dc0ebed2ab93140a31079c802ce54422024ad5e60d158dfb073a87c0383e844ce49e7e466e5c9a92cd7c6fa1a700774690546df738ed7fd4ad445f455367fd862fb14e9187947c5c3f5ef5b283c615427332869afdbf8e4f98963e4299060de865f63ecc708759507919fc666cac2f3405f87f7828787477c99092d25559f8ffbece3d0ca053e5aaaccb6316168650723ae26287fddccbb9ffe4ab2679c62f9704c608f5d378bb2a33a045a2502570c456bd75879b346e8c1e46cedad01abb7d5beeac42c1d765fe2538ad1bc22d5289a8d46d06ef58d425fc66b30825f3450cd339f9ac812b43cdb227c6dc526ef34754c124329cfa761e487efd21f9d12d3243",
+ "0xf89f0e830bd84c83013c66947f5e3ec5da86a0afe1dea0c532978b4179927414830c14c3b87b0000000056000000000000d200008200000000f50000000080000022000000000000000000008c00000000008f0000000000000000000084af0000000000d7ef000000660000000000ae0000000000000000000000e9cb0000fc0000000000550000f076020000000000000000000000890000000000a04f000000",
+ "0x01f8aa010a8309fcc1830333ee9499c26af054ec90f4e03e6fd76f3280f1dd5cd92983069f62b884000000000000e571c70004003b00000000005560850000cc0000db00000095000000ae000000f00000005bd200cf76006500000000ae11d800a700000000470000005c00008fbc0000170000380000000000000000c10c1200000e003cff00960000000000000000350000f00000000000000000000099f7000000000000000000000000c0",
+ "0x02f8fe010b8306208283055375830935a194e62e0981bda74666952c1d1a6a26d7ab6dbc9c698309f306b8d400880000e500000000006b26ba00000000000000001bb2eb000000000000000000990000000000000000821700000043810000bc00000000000000c9000000000000df00009200000000cd00001c00000000000000000000004800000000f100006d00000000000000b9000000000000000000003c935e9e2e00000000c60000e5c80000006300000000080000fa000000007600b6093100000021000000000000004b000000b72a726d0000e200b90300000000000000006b380000005600c000000000c80000a5000000000000000052000000c0",
+ "0xf874098303c86682e0ec943825e24a9a3b098960ce0e97bd3075058ad5d132830c69b2b85100001f0000000000000000000000550000df0030e7000000000040000000009482000000000000004f00000000f100006f000000aa00500000008300002b00eee10000d5e5718500000000000000831900",
+ "0x02f8910101830232a6830498e883063b4f949e27683ae66e02d0591631ceef57a57caa5bf06883083741b867008c00a300000000007354f7000000006e5f000000ffd8002db100fc000000000800000000d29d0000970000000000000000000000000063000d31000000dd005a000000000f0000ba0000000000ec00c30000000000d40000df00000000000000000000000006c0",
+ "0x02f8bc010e83029d0b830dbea08306ca01944c5d29fcafbf9cc126d69e0c31ffea1ce512a4198303daedb892000000006c0000c1000000005f4a0000000f000000d5e70000c8a8000038008c10000000000000556a000000000000000000000000c200005000000000000033000300000000000000009631008900000000c80000000000000d8a00ac8d09000064a5af000000570000000000000000000000cecf002e00000000000004000000002200000000000084000000a200003100c0",
+ "0x02f8e3010e8308dfe4830a448f830defde949815da192edaf1ade18c437d1b229e8743b1431f83042cc5b8b900000000000000f300000000000000001d000000000000000000000033000000000000000000c9000000100000000000070000000000005c5ba00000a8000b00000000000000ca0000004300000000000000dc00090000000000bf00000000007600f4009000c5870000000000004b000000000000000000000000000000e0000075000000e8ef0000000000a600000000009900bb000000001d000000000000004100530000003a8e00a10017000c000000000000460000f2c0",
+ "0xf84b0d8302c81f827d7e9445fd81ee5e431b3d8efb7fe39a227bc3efa0a20a8303f712a93968000000000000345500000000000000000c0000000000007ff700000000000000000038f00000c4"
+ ],
+ "l2ToL1MsgHashes": [
+ "0x14379083f0eb69b13b9e598bcde508d02d4c9ee4a886f11689b06f7ca98ec7a6",
+ "0xf1f3d005d8056dfa5525814ffa080f8641137ed18ad62e6967ae9a0729f84c1c",
+ "0x79c07d61dbd6c9138b677b2bbe5c409040ccd91c9e57a3b3dc4bf7fb1f05e9fd",
+ "0x7b2c5c5657687f62f3501c514bb61155ba99f10677f0dd679f6a133019ea568f",
+ "0x63e4e1fdf1735ad5965356a369c0b5270ac5f3fd57df1253c8597023fcad0ef7",
+ "0x4b8bba266f299d1271174d4d7539458be1ae572d5f2cdc873eb3523bf9a1bead",
+ "0x71d0fb688c7080ad769c68159b2a840550bb509fe2c56654e9b2e98def8a3b77",
+ "0x799b92b7ae23e4e45d7f3c285773a1e94fdc9aadf0724ec3f0dc9f4a0b75069c"
+ ],
+ "timestamp": 150000122,
+ "rootHash": "0x60a1c016d26f3f6255dfbf39a0fa558463ccdda31156a6fc6b47f1266956895f",
+ "fromAddresses": "0xb811c93922d584b136829242c16ce942b1410037fbd804f43a221eb93f30670ce2733ad9ba63244459883bac792370c268757a381e8091e5df191576f6815c48f9718a7f0c4b7c05208ec940ba428535ab4287b22975e2a35bc6099b8c0d68dc8c04bcb743e3d369bbdcae67906d4795803d0519b769c45e4684f75b2623f7e3ba8dd47d0dcf0cf1c90ac07b98e83386d2fa73a46f219585726bcaf55422a2fb71afe76dfc49ff5068a1f7375792553bf49e0ce45d13de295e43da2689c480999ae4b0910790ced21b9763e0eee361d24a76407cba490bb25ce27e10",
+ "batchReceptionIndices": [
+ 2
+ ]
+ },
+ {
+ "rlpEncodedTransactions": [
+ "0xf8b206830e679e824465942a8e907eb4c9fb898496197648a2bf2cf3beffc683057820b88f00000000340000000000d6000000e50000003ea10000d50000000000e300000000b60000000000b900000000000000000000000000000000af00b200008b000000001600000000070000000000004f0000000000006f0000f50000000000000019000054000000a7d900e500000000c2cc000000a10000009a0001005700004400000000004b000000000000000000",
+ "0xf90144098302d34282db78942cdcc894e38f9f4b2a52324833a276647b8ad0c5830c423bb90120000000005a490000db00fd000000b300680029e42f0000000035c30076f40000000000cfb80000000000000000000000e20000730001000000002a000000000000000000000000000000000000c07c48000000c20091009b4800005f00000000b5ec00000000000000b5000000ed8c00000000e7f6a100009f00000000000000000000004c00df000000000000000000000000000000000000000000000000000091000000000000000000003aac00004c000000000000000000000000975c0000000000000000c800a70000000000a0000000000000000000af000000000000000000eced0000000000db000000000000000093006c00000000000000000000000000000097b70000c46c0000800084f0000000000000fb006700ff009f0000",
+ "0x01f8f1010783021055830ea97b949bc170a5278af7ef30d5d498fbefa690435cb5108302c7bfb8cb5d00008e006a00270000c76700d5d800000000000065009a0000000021000000000000000000000000000000740023000000000000007500a600875e0000f600000091fe000000000036000000000000000000007700000000d6000f00000000000000000000000000000000000000001100d70000000000001a000000000000a9000000008d000000000000550000000000fd00000000250046f0030000a6000000dd00700079352700840000000000000000003fb4005d0000bb0000000000000054cf00020000000000c0",
+ "0x02f84f01018307b4bd8309924b8307e44d94a06e3973ad966f3edba11663a8e5cc0a30fe5f02830cd05aa60000000000005c0085a7002a00d537009500c43a00000000006500008300560000000000001ec0",
+ "0x02ed010983093ccc8303fcd78301a82b9468a0f3110c69ce57d3e3c9f31ef521e78cbd6712830b7b9f8400000000c0",
+ "0x01f90136010283017596830dc7ea94df27f8e73161a15f873e28e1fc78400dd74ef52a830b58d4b9010fd6000000e80065000400000000fbd50000002d00ef00000500fbf7ccb900000000009c000000000000c7000000000000000000000000002a000000a789db000000a9000000d90000000000000000eb000054f800610000000000d1000000006a0000fb0000007f00b3940000e700170000000000000000000000000000003c00009b24310000931200ce0090000000b8b60000000000000000009000000000000000000000001600ce00000000000ec2000007141bc8000000000000003100000000000000af008f000000520000e72900000000ff006f00000000000000000000000000000000000000a1000092000000000000940000960000000000c99b034d0000003a0000000000006f0c9c00c0",
+ "0x02f8670101830681af830bf7ea830aa58794f0ac349d4d50cc481f637478c2a5594522d5ee4b83059358b83daa003700000000000000000000000000000000000077007cc600aa000041000052a34b00000000000000004000fde50092006d00000000003a00000000c0",
+ "0x01f88c01068308032083099d3294060efa832e46e6bb95a562de7a3299f22629299a83089e45b86689cd001c000000000000d900000000f300000000000000003e000000000043000000f3000000a4c6000090000000000000000000000000000000f80000000000fcd10000d6000000000000c10000000050000032000000d00000dd00000000000000d2000000c0",
+ "0x01f9028701038202608302dbff94c499a572640b64ea1c8c194c43bc3e19940719dc80b90264f4b476e1000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000114f39bc318a43de8bdbbb61dffb285e20a9c3fc5247afd00ba43702cedc25eb3cf222c4cb6ece067fb562d4cb1bd1cf43b8c537006bff2b107f2aa71ce255eeb3d1f94164de6aeb234179a85ecc19dbfeba34adea56a664cfa5dd70fe7021b2df7b5c58330da913f7f6594822b22d8c20907534b21c4a2410d74582c94c8a9c97d0700283ae8e79a4a0751e7a5f454e368e1ee15bb9f798d8fa85d47aee27e7922eac09abe42856dc3be1b24953d8a027fca124a7f9c1442d16e48d9b436023d92ad0d61e76f150d9d627dcdc9ba09da9dfa8e63baec0db5f8d0d1e3089a46b92699bfebbdf13de544d4e63f4a23228f06db1c7451fdf3a2c97d501bb73445e2cc2ea290e4b5041524b475c9d4daeb0cf728caef993fbf565ebbffe4394065a591734cca937150c0eb92c59ec4df7881d520ac6bd34a685456782cd97fbff57a6d5a2c2e1bdac8cc21720157d5f6134962fa11d8c5289fd8aeea8b5000c0fa6fa143f20e97af6c654ef6fdbe634158ff962dbd1d897302500784191d2500053bf4c3473aebd0316d8306e3a5f747ab01fbb8514eccee080b1b16e9d0f38a25a507099d3a7aa46e1f39b75502962f773659d6adf367daf26d8dc2eb4a338824fa2353e757a30e55083714dd984c6d24c0547a69535a8e3c45bff060403351664a68900ecc29a11f7a1a5437734f6c9470b4634988561c97481a42d7f519f72e5a74f90a1f10e67abd6ff3c3ac835c10dacde3bfa1bd0ddda9755998d0beaeef082c02e0c58b6497f4a9fb48e69969cfd2549c56b0397",
+ "0x02f885010782466e8303735c83039b5094744200c88342cf57085887a6f54c6c72b02235038302db83b85c000000c8ab00a20000ba75000000d6000000000000e30a00000000000000850000000000001cf00000ac0084a1000000dd5e000000009d52000000000000000059000000000000f51d00000000000000ad00000000000000000000c9c0",
+ "0xf8740d83074fc18237e2947510ba0479668d5dbbc5553bb71379a07d67e03f830c6c6ab85137000000e4000000f5000048cd000000000000000000000000ca0012f1007900000000000000000000da00000000ea008d00b0000000000000000000000000000046000000004a00005fbf00000000ee00"
+ ],
+ "l2ToL1MsgHashes": [
+ "0x78348e66c891cd9077a7bdfb1356434997bf36afb6aec903a8318f114962b9cf",
+ "0xfa1a83e5a347bbbf2f8efb63d4180c6dc755d3d927c961f3fa7e5f09975c36c8",
+ "0x6f640e857104ff2fb34f8bff2d8354a148ba26fa6c25ec3b4d7469b1d7985815",
+ "0xfdbcd73df786af4b5d5b450438d0a1c0cb8ac3ce3e836baf9bf5080d84474dd0",
+ "0xf6be52cb91bd0ce5e87109fdca4d475e3f677ae15e5163347917c3a7435ae0f8",
+ "0xf66d6f609e73460a1eff51dd502c5c3a5443269deda18cda8505f5d56a245738",
+ "0x232ecc0c5d3e011c9549ee1fb0cf7919817ccb7f504b339605d493153f8fdc0c",
+ "0xa0577b6cf1a2fdaa5f0881e6f69b278ff496fe21d496914a20092a38fb091790",
+ "0xeaed02124d7458c22cd86af990a4350a307dcef47afcbf7acefa3d67b70e16ed",
+ "0xdd7fabbbe5c4302ec63a997cc9e444d729aa0d641bed9f8f19360fb2d3555020",
+ "0x38ef6fa7285848322e6358b21f3bf4c1913807c91232b9bd4af88e38affca989",
+ "0x3d3a7e2ef5a346d3087580c99aef1b1c5db201a4fc5fee93e5db4d94f0f6712c"
+ ],
+ "timestamp": 150000143,
+ "rootHash": "0x9ad2bd906dff6577355da8d693a6066d36cd972ee6e1d7d9c6e029f23092afb8",
+ "fromAddresses": "0x1b35512be8364796ac7137516572d68104514144c0c0bd44a47bba0bbd7c2dc7f329398bfb1df2c040ece9859702104568a693823deeac9a71e43ce773001c3516e1e6200aa954148a81856c3973f3217e360f12be68582f7ae8b0fa4cd6e3eca9e192539d253b99b074725244f6dda605b66718c363f9dacfbb639d0494ac2af07bad4cfc82783e17069fd0fccb67ee581ecab8885be958e7e3202790a61f230a019cdac23e28877cb7c13f3993d44145fc00a367233f7597fe27e8af9ada82da7799b648636221a25fdbaa41eb20883ac11c26c691291285acff39",
+ "batchReceptionIndices": [
+ 8
+ ]
+ },
+ {
+ "rlpEncodedTransactions": [
+ "0xf9012902830c1f1783012252949ebdd083b4da447c858e434820c05b6777097415830b6b09b901049400000000b155df7d00000000d300500000000000000093000000000000000000000000e6420000008500003e1b6900000092460000c72d000000b900da008600005c8a00000000002a00005800006332001400e3cfea000000000000003f003600c5000000000000f800ec0000000000be00000000000000000000000000830000000000000000000000006a000000000078bce2f500fb0000002972007d3fcf000000d72f140000000000000000000000fc0000000000f20000000066000000000000830000110000a00000213900974a000000000000000000f900000000b0000000000000000000b7000000000000d8004600000000004d000000002c0000000600",
+ "0x02f85401098307e04c830160f18304a42294fd7df798e8b6e6c9a76875144b77742fa8b150a58303b391ab00160000000000000000000000009500009e00eb00c500a600000000c000000000f8000000350000000000c0",
+ "0x02f9014b010e8306e62e8309d1788303a61394f67c78d752243d1e24109dc1668a196962a8aa21830c4eccb9012000000000005f000000000000000000a9000074000000000000c300009f7f00000000000000f6008a000000000000840000b81a0000000000000000000000450000000000000000009a29f300000000ff00000000004900c10000000000be2d0000000033c60000b200000075d89a0000000000003f00000000000000000000001200083800000000ec000000001d00009c68bba800002f0000000036e500d6850000fb00000000002600000e04000000002000005a0000210000008c000000086e0000003c00000000000000009c000000fbad0000000000000044000000000000000000f000000000000200000a000000000000000000000000000000d80078000000000000ed0000000014e90000a1694737004b0009f00000000000000000c0",
+ "0xf901570a830e0aad82347e94924680e8944a1def8c8b521dcdca44dacb355752830e005cb90133000000000005000000001600000000c900000037d1920000009c00ac0000000000000025000043e400007900130089000000000000db000000000000f64900180000002600b700000000000000008700000000290000000000f6000000000000005700000000dbea0000000000830000001633000000f90000000000000000e2000000000000000000040000000000003200ea00001b00040000004f5cb50000000000000000000011efed000000008c234e00744a0000600000763600007d002d000e0000000000301d00000077000000000000000000000000000000000000000000003b00e70000168500000000000000a1008d000000000200000000a1005200000000000000f32a00f3007900000000000000000000000000005883890000da000000000000c30000000000ce0000b800",
+ "0x02f8990103830b97c2830b0599830474be94148a5cf72669b62392330f768b42a57f873966bf830ec227b86f0000f4004cc700fb0000000000000000005db20000f4000000278c0000000000b40000000000000000004900a5006290000000005c0000000000000032000000e5003e0000d700ad0c00008200000033be00000000000000008c011f007200530039190000000000f7006f00008f00c0",
+ "0x01f9011f010d82ace78308cf80940e32f35984e8ac0cd66f665536ef286e51eb16798304f741b8fa5400000000170000745200000000000000000000000098000000680000008f000000000000000000000000000000000000008f00005bf2000000000000002300000000000000c6a100452c000000000000000000000a00000093005487000000000caa000080c3000a0000000000000000960000000000000000000000481d00008c000000ac00000000000000002c000000004a00000000000000000000000000af0000a70000000000000000e9f8000000000000aeb87ecb9d00000069000000000b00a26c007d00000000270000008b00007d0000000000f128074c000000000700260000000000000000000000f5000000005500000000afc0",
+ "0x01f8ca0107830a03f38201f094140c84b6c42b9cac53321df0ddb6a8b165a5ff4b8309edc5b8a500b000000000880004c90000001a0000406e0000000000000000690000003800960000000000006b786ad306000000007d006429000000000000a4000000003b9000e5f100000b30d000f600e90000000000000094df0000000000000000000000000000000000000e000000100000006d2500000000000000000000d7000700009a07ca06fc00de66000053000098000000002900860000000000005300c1000000870009c0",
+ "0x01f90147010c83022b208309bff2947b530af2b715bae7dde78ed962a38123da46355e830d9ebcb9012000000000000000008d00000000000000840000000000590000d0000000000000363e00000d0000c7e6000000000000000000e5009f500000000000d200b30000000000a7ba0000f40000180000000000dd00000000c407000000000000000100470000000000000000000000300090f000000000520000d10f4a000000d500e9002100007f00d50000007500000000000000004a59004d7f0000000000fe000000000000000000e1c000af000000a34a000000f8000000000043000000d5b600b900b500000000cc980000000000ed220000d50000000000001800008700000000000000fd0600004900000000000000b000000000ab00000000e600007c000025770000000000e7a114f1000000000000004100ac397400d60000000086001ec0",
+ "0x01f902870103820270830c560394c499a572640b64ea1c8c194c43bc3e19940719dc80b90264f4b476e100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000011decae28622ef6d89d74f21aed766e39bdabb9be6bc54c15c1b10f52d3d4675d6f9be774457d2969a5615b4036a726e4a7516ae33424e12043707a6f3d49d0d2c5a2d94ed08c8427dc8091405c715275fd20a9ea72fe5e59dbb0f53929c9f41cf0ca0415728acd10cc00a1a05af9dfeaeb9f1b3554d74bd4c440b9a388ca23e6ebc954d1143ab61ab6295b6cf081c22c54a288a38b4b4874b4a7030e3e21bee4d9c4bda807d5ecbc052b4c8c235738492ec95d548cb2a931bed6788a02cc74a8386826306274ecac1dc1967ec68b378a28f26215c532e63ff4fceb0da405a362c8b33a905bd617a95cd28763bda43d31b426dc26dbc78612a68fad4d2724c2441a478befc8c3e1419aaa4b95efe4809a6a4abe0bbdd231e2d5024e7db7a2e3e34273d8c2012675b1e3b211d037bfd40ebba4f5e65983831a664f59e51f1dbd768b3a163bd54520d74289f30f9d016245a4d11ef1b7786aeaea1e5e0eea8ca71ff475bbd1e20385f6041dd085731d1d64901395e2d74222cd7d60767a7d007b197d6fc002a3d98177583bcb1832e90a7c67c7d3cd7c0c3a68495fd759052d8678040ff3088976846dc2ea89b5600d3c9bc2a904f13ad51bb0fa74ee9b4ce8644b14262faceebc1b41e44a6ff7059de775a57cd8e92a45e70b20028238fb953df88aaeab4b3b7ee9f0b1187b73ae7a899e5cac4f221ae34566f9f565209900c161e36d350d56ffc93b6fc01c36addb5e3cc677bacc30f8543d2efcd1321dd3d7481c025b4e1e5e74ab528c4178208b7ab3ce48ff705c1",
+ "0xf83b018303c69582a163940411dc450576ea25887f3dbd13888040e1054415830221e5990000000000004c0000000000000035000095e90000450c9a00",
+ "0xf8f9098307750283011bce94df4b3411cf590b9ced3923e0ad22d41e6ac52ebd83029c83b8d50000000000e400fd58000000f50000007403a70000000000000000000000b100d100b876000000e0860000000000000700000000002300000000003f0000056300008d8300000000000000ae00000082000000000000f5000000005c000000000004006600000d0000000ff600bb790011e400000000002200000000f60000f487a840000d0000005fc200009b4000000059000000d6000000c700003400000000f700e600000000ae00000e000000006f000000d1002a0000df0000006000ef000000000000000044000000000000004bea006600"
+ ],
+ "l2ToL1MsgHashes": [
+ "0x176536c4b0ded200bccb6a27364391a1aa171ac513325dbc799fb6b30f016b57",
+ "0x16d646d505da67249b7b7c87d56fea8d54e8105f587873afda26cf0d894945ff",
+ "0xbc079cb5af90459dff880ea0e9d8eec3672bb3ca9d5b92dedb51d9229badaa24",
+ "0x38aaae102afaa78d29cc80d79fe76093b70ba957f8f2329f323734b831e01df7",
+ "0xa5767cfba4181bd54a98575ac3555e15304d08bf8c5831e17d15a78fb2f2802a",
+ "0x9a6142a120b0b203ac0a885b50c0b841ed985a9b677547f0f3ee29d5321a402a",
+ "0x0f31dcf03d04f73782f098121460fba0b8ba6f55a6301c91cc4f78e4bfe8c7e5",
+ "0x86d24401505a9cb124f7f522f44c3b9cb08f4224eab45fce9f16889fe4b8c725"
+ ],
+ "timestamp": 150000161,
+ "rootHash": "0x4d8364cc23c47bac07bcc9bc2b6fcf0d5d1459d25ec5e134a591d73f3a6287a0",
+ "fromAddresses": "0x4a49b67b8087272f27ecd7b2c8c999f1e712f9adcdaedb25b4399426120d50261f0851696a602142dcd1a37e23bb63aa9ac7eeedab9c5d6222dfb416ca7318d2c24a9601b620e113148784aae03b737c9eec5cf61e9d6ba7f3e95095050d35985bfc1e5c1afdd54f50f36da90e59de72468de02fbd1c40b11f9636be7dbe7e665fab863d7429ff512b30bc4d1aa56dcdd0de035a3c7bcde96b7c1845695c7b4064b6e94fa19e1bc96d1043d16300f3c0ea24f15fe99ebea6718449489d69c41a76a437c0e3f3444b644b294bb00ea26121351228364c869ebfeb5744",
+ "batchReceptionIndices": [
+ 8
+ ]
+ }
+ ],
+ "parentStateRootHash": "0x11c632ae819c01fa4273277342a1e4abe6ce0e49ce4b071da695d47ffb4a0a45",
+ "proverVersion": "0.0.1",
+ "firstBlockNumber": 5,
+ "DebugData": {
+ "blocks": [
+ {
+ "txHashes": [
+ "0x4cb194445c46e8782527f0a8cf2660091332c1af4026907d9af0f756bd7278b1",
+ "0x7a4d1ea502e98c82f037414bbb5ba9bc77746f6c7a0d561bb1b6b8bc8141c251",
+ "0xbe1bb5b509e170b8821dee8b4e94bfb0cbbd8299df1f7bd5f22cd4d5c10708af",
+ "0xa2d2d6cffd2421ed24e9b36475e41b251da983e48f96dc3f4cefa863f1bcc418",
+ "0x2d13b20a46f26a4bd016f3710a6b0f8c1e6f9bea211233843cab259292d5e97f",
+ "0xad12a249426beb6f23ce7fef64ba4b04913718a8276d2a8de849b47ea7d61eaf",
+ "0x20803a6d4edaa537b7c64942f8a93981f2fc22a4abefe8768bc79693cf2ffaf3",
+ "0xda66ea24f074e714263fdbdeb7d71ebb100bb1940aa19db33d5d839ca9114cc8",
+ "0x54805cc17f4bf53196ec0e98f86748f538485445b2fe454b472d07e86a9f2f9d",
+ "0x80e1c2be5c1cbe7455d2ad3e7c412634f8de4a1e3a74b5c015604450aae72f59",
+ "0x4d11cc814298013d72038cd8f4ca5d11d2255732469438b8a8dd0e0fefd7eed6"
+ ],
+ "hashOfTxHashes": "0x2e1bb57c2b1e68b5a8aa9a28ae44525932d60d43f06e2d7128072d9a9c3389bc",
+ "hashOfLogHashes": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
+ "hashOfPositions": "0xc65a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a8",
+ "hashOfFromAddresses": "0xef815fec857c86ed5a0e8cbf17b01015cc31bd5a0e912a8eea9831d2cb8d8988",
+ "HashForBlock": "0x2e3a48f22768998c1817343f39d19e56172f306e1628e8bbf35467c072f54f26"
+ },
+ {
+ "txHashes": [
+ "0xb62a44eccacf71bf46e2f9c69a77dba03ef39399c5a55821e95e01f1143468b2",
+ "0x8c499cc48efad87241611937e61758b384c90352237ff9b8b6dd3b8d1c3fae1c",
+ "0xd50842c168df4801f942315cf9beaeee73ae778d0f9ce1cf270c1231e8c7848a",
+ "0x7a1c3e806a0aba17c6f93bf13c9f175b11841b113f11a8e7d4d8cbcf9c0723e2",
+ "0x304b03d3a053989c13a6b4463917d1a8ac9e296b64604fed5506e6bef1e1cb25",
+ "0x59d6d37abfcf51549426e06729a63b6de6cbb24be1bb7ae653346755e3f57a79",
+ "0xbefaf83a1422511d98d0b0813fd839abd203f16dcf118c57fd6d074f5db82eac",
+ "0xdab3d10db9e8d68c07eb92c98de8abbb818fb81b6b39efa957515538e0cd8eca",
+ "0xf6ef3a3158973b8eed997611181d9741e49474f8f8599dae4b8b9fcd1b7812c2",
+ "0x1551e76fe9c1296bb310c77e4172f7e9926ea106590d717da5552666aa9c46a3",
+ "0x501ae78788f144583fa0e096df1d2e2929a5e99cbbd59b65996b3312e0805c65"
+ ],
+ "hashOfTxHashes": "0x3cf4bf020ab4dea021de498e2b1820c77674e8da4ef4bd26ddf044647135b5e4",
+ "hashOfLogHashes": "0xeaad1964a0bb9c36b2c14dd60435eb4f89f927a50148813cbcebf020dd5e46e4",
+ "hashOfPositions": "0x405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace",
+ "hashOfFromAddresses": "0x9df674170866014e47bb131956d485453f5e7e129243e7adc662b607438fefd8",
+ "HashForBlock": "0x445bdef06c63987b60f385f60ad16d01eb48efb55fc3e12ac7fdf9fa37e083d0"
+ },
+ {
+ "txHashes": [
+ "0x15bd2814812749c2fccd4e06a1d1655eadfd6120db790ebeea0f67ecf051fbd4",
+ "0xd109e2728050e268b9a6c781621c3ecb76e5e35a470ad40aa7e4484f5d019ccd",
+ "0x3f5bf4dfd1c066af5f7e13eb17faedc2003de13494426d91115af62a6b113ad5",
+ "0xefc9bfef83c32859f87db708d2f0848cfca6c63e9992c7f2b34b4c77c748850c",
+ "0x8399749e08334bf22246c9b7c378546cd5fc6b51872c7f0c0d53a1851858b5f8",
+ "0xd69a86a1ba4298789e0a3925baa1c2b6d2548eb743272581180b5b598807679b",
+ "0x7979c74f3c44750b766b844906a64d05ffdf9792a31f0d558f7fdab3b150cdc7",
+ "0x5456477d23be7dd6dcfcc5a1674647976c53a0a5e941357f01a73c1c2b93b57a",
+ "0xc6279c15b63cc3ee72a1aa0d58cd316744f8955982a9067edaaf3674e35213ae",
+ "0x4df55f2557bffbb758a8ce01c8f8401e65f14cf582ffa8490ae65a2343db72b1",
+ "0xd61ca313b25ac059490939763fd4e4ecfafda4c87e099adb4aa483e717a58b8c"
+ ],
+ "hashOfTxHashes": "0x3b1582c5978ff4cb67c708a5e92442c65736a293c8e41137ac5e72a7b73ad1bc",
+ "hashOfLogHashes": "0x3f7ff5769809a4148f3aa225ea7303784ace4670468f989622fbc629bb9875b1",
+ "hashOfPositions": "0xf3f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee3",
+ "hashOfFromAddresses": "0xf3128d03272f8d5dde6ab024d56dcf7b93a4340e684b7c1da8cd7059f6de6a32",
+ "HashForBlock": "0x3273a49597c2d8ebe48403dff526a5c7411f2945180784959993f513d95fd6a8"
+ },
+ {
+ "txHashes": [
+ "0xa7cc3103a1421b0f29f9ff84951664eddccc3b7a94e8ecfadb70767b6392f841",
+ "0x6857f32d676f93172e34f131b37942c795db93e0c089fc26aef88acbd22b72b3",
+ "0x1db299b5f492f6a991dd58e0ea8bdcb448f0ce590a853471fb49663c86c2d5e0",
+ "0x62d1a1dfa92384c8a07451dad2e6b083ec472b9db357628c3b552f9b707999c0",
+ "0xfa0c94496ea03c77ca06e34dbc2dc04977102d1c88352a142e427b425da720e0",
+ "0xef49aabda9b462d47f631215177de1933b8aa31978f700ac2a4e164c1a892763",
+ "0xde1bd05283c56e69fc9caa60ad913d896c2e3460c082d507895663d23c785398",
+ "0x3a46f94098267bed028c53b2de5e3fd0bb678a446340d0b72afe53b7fc2d1e30",
+ "0xafc9b1a78f9c51018f33bb286c2656be1f48fa08a296b04f722d147e5ce5612f",
+ "0x40621b56cd88393a090cb4751192397a71c25f673bae616e2417f6e998404ff6",
+ "0x79107c5799b2224e13c0cd48a78a944d9bcd879fd40a3813ce834ccaae28704d"
+ ],
+ "hashOfTxHashes": "0x4ce87cd84c2ee12d717abeeb32dc7f878befb3bdce71b8c9412907aaef32c8d7",
+ "hashOfLogHashes": "0xdd6e4ded7049f9950abb71280e350fb4101a9ada9e14dbea7393a4ca4278dee6",
+ "hashOfPositions": "0xf3f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee3",
+ "hashOfFromAddresses": "0xd5ef26187934a987c6e5543177a84aaf4c5ec96b850cd8ca2861901a51a80cb8",
+ "HashForBlock": "0x618810e3c5aa64ca08204a8898de34e8799116750fad316b4b16a73cd902e3cb"
+ }
+ ],
+ "hashForAllBlocks": "0xac07a52606c248c3be9e7709709fd8878e1312db7c6d81de363581496b92a6f9",
+ "hashOfRootHashes": "0x9797ec2db654db9414d13d89571ee3a7eba0b9821d8dff7d134015de379fdcef",
+ "timestampHashes": "0xcd286c08e88d7406c4956b9e21e514e20fe1ffdbc2dae678e86487bfa7d9b1a9",
+ "finalHash": "0x2f2cbcf22620ac5ab79b026aca79ea0982c6a99a28ccf08a94cac0e2c856d76b"
+ }
+}
\ No newline at end of file
diff --git a/contracts/test/testData/test-transactions.json b/contracts/test/testData/test-transactions.json
new file mode 100644
index 000000000..5c8750355
--- /dev/null
+++ b/contracts/test/testData/test-transactions.json
@@ -0,0 +1,63 @@
+{
+ "shortEip1559Transaction": "0x02ec0580788301d4c082520894cbfa884044546d5569e2abff3fb429301b61562a880de0b6b3a764000084abcdefab",
+ "eip1559Transaction": "0x02f90288010b4d8202918301a48a94000000000000000000000000000000000000000080b90264f4b476e100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000011497569c1f1e97fb9eca5b5f7c7153f2eb6f9edd312bd4d0dc529f15ae67a7cbe748b2de11e9d5335ade6f0e71c1254c3c23a8216d6e2419113eb8192a5187eddd9516ba32d0f1e22fb90e7dd589fb6ae6a923494d76fbe94bd4323376094a0220f3befa0ffb169aed6e7507033e47c4f0713007d92700ddc0269a359a916cf885e0187924b63e66ff153e6f62c7b5ea26ae1547f531b3ad9e7d9409f8c3136d610202aedf6eac4af0e8387c1c126f4e516b993fa03e57de3bca558c2e54fbfeec19328fa35cb9dbd4b603faca0b50cf1d5d358e7577cded0a31c5db46738b3f1a738de163ff4ad824eb8b86a0b8e3da8d641c0cdaa2b373dfbb3a839aecd1088a417024abdd08ea65e7deea12cefa7881d48bc10be0bcaea05faf43f135a062a956eb652a74fb07fa209e35ef77d2f55dcc8131e4ed72b8255504838b46e0da1006185e4fb52a608c1af78c564cbf7363bf0bc36a5aaedc3914ce2c03c8494379b60c1170beea77dff698e236e5518d4258e55bd6f0ba8ef60dd1b11cd59788c232724d04801f06907fb8e260bfad73f3eb6c7803aae3b1311d64b9815989aef11fb6cafa22ecaf61018cf5d1a3c39d3962ebaaab54c81970148d6f609530a848e2aa66d05b9cf1bb35642eb1b8a637a82be34ae5e24e8826d99fe2f726c348ce10e03b929f0f53a2f687e39499eff77dd1d9a90ab456e1be9f861cfe1a65f26a78a31083134be977ea6a98640270e8a1fb554aa77e20889972d7f736bfe1ea5c0",
+ "eip1559TransactionHashes": [
+ "0x497569c1f1e97fb9eca5b5f7c7153f2eb6f9edd312bd4d0dc529f15ae67a7cbe",
+ "0x748b2de11e9d5335ade6f0e71c1254c3c23a8216d6e2419113eb8192a5187edd",
+ "0xd9516ba32d0f1e22fb90e7dd589fb6ae6a923494d76fbe94bd4323376094a022",
+ "0x0f3befa0ffb169aed6e7507033e47c4f0713007d92700ddc0269a359a916cf88",
+ "0x5e0187924b63e66ff153e6f62c7b5ea26ae1547f531b3ad9e7d9409f8c3136d6",
+ "0x10202aedf6eac4af0e8387c1c126f4e516b993fa03e57de3bca558c2e54fbfee",
+ "0xc19328fa35cb9dbd4b603faca0b50cf1d5d358e7577cded0a31c5db46738b3f1",
+ "0xa738de163ff4ad824eb8b86a0b8e3da8d641c0cdaa2b373dfbb3a839aecd1088",
+ "0xa417024abdd08ea65e7deea12cefa7881d48bc10be0bcaea05faf43f135a062a",
+ "0x956eb652a74fb07fa209e35ef77d2f55dcc8131e4ed72b8255504838b46e0da1",
+ "0x006185e4fb52a608c1af78c564cbf7363bf0bc36a5aaedc3914ce2c03c849437",
+ "0x9b60c1170beea77dff698e236e5518d4258e55bd6f0ba8ef60dd1b11cd59788c",
+ "0x232724d04801f06907fb8e260bfad73f3eb6c7803aae3b1311d64b9815989aef",
+ "0x11fb6cafa22ecaf61018cf5d1a3c39d3962ebaaab54c81970148d6f609530a84",
+ "0x8e2aa66d05b9cf1bb35642eb1b8a637a82be34ae5e24e8826d99fe2f726c348c",
+ "0xe10e03b929f0f53a2f687e39499eff77dd1d9a90ab456e1be9f861cfe1a65f26",
+ "0xa78a31083134be977ea6a98640270e8a1fb554aa77e20889972d7f736bfe1ea5"
+ ],
+ "legacyTransaction": "0xf902840c8185830626e494000000000000000000000000000000000000000080b90264f4b476e1000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000113a3994889f43c8ecfa4baf6368da28c518103ea2f63141b84f44023cd15a6608474f80226e588eb1706afd1a94e4be31dcc751c278bffb4745761f7e30501788db3ec15b74f32a6baec7ce2694bd84d4d092acb5fb7372fef5102208860a4b911121774ee0ade8670b61a0794835ed3cdccaae1a50c85315599769018fd134738e9b0f5a83902fdd24ea9572034a85415a1665186634acdf3ed194d8b21cb33be95456ed5a5c58bc9951203405295662807261a27842dd13136a37a4e41fcc279ba17a405e33c9490892a9ef25f7ead8d02fd071ca1104fdc2862431e033cad2063d4473b559daecaeda648c784f331a09c3c6213a3b932d457f4c524c9590d87e9a5539913b4b7b2ee55c578b52d400c4865c6226bd4c0d0857739b6ba8be1cf8d9d209683421b302d7e4fe89d0fce0770aa26d70a960d6b023ecb50c6d2117c52a850feb0912c22adfa8f728c24b9375a4f30286a0a48031be2e99188819b55efbb6b8db25492b9288e97e9db5834140eb3926a9ef430d9005f708725eafbebdc5ce513624ff6602a8fb71fd77a007883c3edd0f52faa0d388d46cd51c88a20a4a67441e24bc24d417239a53bd938d00987ea74138f65acb9b401e6362c926f720431e778c20adee2bcb535f1cfc79d3f48a40baa81fdbf05c7449bd3563700a9ed816bf71cb78ee57342ca8e69fefa8e66a9b2579168c6b86de7eedaeadc87fac6ec822a5943c4d04f74a1e637293c824ddef487582ee53250957afa40ae5",
+ "legacyTransactionHashes": [
+ "0x3a3994889f43c8ecfa4baf6368da28c518103ea2f63141b84f44023cd15a6608",
+ "0x474f80226e588eb1706afd1a94e4be31dcc751c278bffb4745761f7e30501788",
+ "0xdb3ec15b74f32a6baec7ce2694bd84d4d092acb5fb7372fef5102208860a4b91",
+ "0x1121774ee0ade8670b61a0794835ed3cdccaae1a50c85315599769018fd13473",
+ "0x8e9b0f5a83902fdd24ea9572034a85415a1665186634acdf3ed194d8b21cb33b",
+ "0xe95456ed5a5c58bc9951203405295662807261a27842dd13136a37a4e41fcc27",
+ "0x9ba17a405e33c9490892a9ef25f7ead8d02fd071ca1104fdc2862431e033cad2",
+ "0x063d4473b559daecaeda648c784f331a09c3c6213a3b932d457f4c524c9590d8",
+ "0x7e9a5539913b4b7b2ee55c578b52d400c4865c6226bd4c0d0857739b6ba8be1c",
+ "0xf8d9d209683421b302d7e4fe89d0fce0770aa26d70a960d6b023ecb50c6d2117",
+ "0xc52a850feb0912c22adfa8f728c24b9375a4f30286a0a48031be2e99188819b5",
+ "0x5efbb6b8db25492b9288e97e9db5834140eb3926a9ef430d9005f708725eafbe",
+ "0xbdc5ce513624ff6602a8fb71fd77a007883c3edd0f52faa0d388d46cd51c88a2",
+ "0x0a4a67441e24bc24d417239a53bd938d00987ea74138f65acb9b401e6362c926",
+ "0xf720431e778c20adee2bcb535f1cfc79d3f48a40baa81fdbf05c7449bd356370",
+ "0x0a9ed816bf71cb78ee57342ca8e69fefa8e66a9b2579168c6b86de7eedaeadc8",
+ "0x7fac6ec822a5943c4d04f74a1e637293c824ddef487582ee53250957afa40ae5"
+ ],
+ "eip2930Transaction": "0x01f90286010c8185830626e494000000000000000000000000000000000000000080b90264f4b476e1000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000113a3994889f43c8ecfa4baf6368da28c518103ea2f63141b84f44023cd15a6608474f80226e588eb1706afd1a94e4be31dcc751c278bffb4745761f7e30501788db3ec15b74f32a6baec7ce2694bd84d4d092acb5fb7372fef5102208860a4b911121774ee0ade8670b61a0794835ed3cdccaae1a50c85315599769018fd134738e9b0f5a83902fdd24ea9572034a85415a1665186634acdf3ed194d8b21cb33be95456ed5a5c58bc9951203405295662807261a27842dd13136a37a4e41fcc279ba17a405e33c9490892a9ef25f7ead8d02fd071ca1104fdc2862431e033cad2063d4473b559daecaeda648c784f331a09c3c6213a3b932d457f4c524c9590d87e9a5539913b4b7b2ee55c578b52d400c4865c6226bd4c0d0857739b6ba8be1cf8d9d209683421b302d7e4fe89d0fce0770aa26d70a960d6b023ecb50c6d2117c52a850feb0912c22adfa8f728c24b9375a4f30286a0a48031be2e99188819b55efbb6b8db25492b9288e97e9db5834140eb3926a9ef430d9005f708725eafbebdc5ce513624ff6602a8fb71fd77a007883c3edd0f52faa0d388d46cd51c88a20a4a67441e24bc24d417239a53bd938d00987ea74138f65acb9b401e6362c926f720431e778c20adee2bcb535f1cfc79d3f48a40baa81fdbf05c7449bd3563700a9ed816bf71cb78ee57342ca8e69fefa8e66a9b2579168c6b86de7eedaeadc87fac6ec822a5943c4d04f74a1e637293c824ddef487582ee53250957afa40ae5c0",
+ "eip2930TransactionHashes": [
+ "0x3a3994889f43c8ecfa4baf6368da28c518103ea2f63141b84f44023cd15a6608",
+ "0x474f80226e588eb1706afd1a94e4be31dcc751c278bffb4745761f7e30501788",
+ "0xdb3ec15b74f32a6baec7ce2694bd84d4d092acb5fb7372fef5102208860a4b91",
+ "0x1121774ee0ade8670b61a0794835ed3cdccaae1a50c85315599769018fd13473",
+ "0x8e9b0f5a83902fdd24ea9572034a85415a1665186634acdf3ed194d8b21cb33b",
+ "0xe95456ed5a5c58bc9951203405295662807261a27842dd13136a37a4e41fcc27",
+ "0x9ba17a405e33c9490892a9ef25f7ead8d02fd071ca1104fdc2862431e033cad2",
+ "0x063d4473b559daecaeda648c784f331a09c3c6213a3b932d457f4c524c9590d8",
+ "0x7e9a5539913b4b7b2ee55c578b52d400c4865c6226bd4c0d0857739b6ba8be1c",
+ "0xf8d9d209683421b302d7e4fe89d0fce0770aa26d70a960d6b023ecb50c6d2117",
+ "0xc52a850feb0912c22adfa8f728c24b9375a4f30286a0a48031be2e99188819b5",
+ "0x5efbb6b8db25492b9288e97e9db5834140eb3926a9ef430d9005f708725eafbe",
+ "0xbdc5ce513624ff6602a8fb71fd77a007883c3edd0f52faa0d388d46cd51c88a2",
+ "0x0a4a67441e24bc24d417239a53bd938d00987ea74138f65acb9b401e6362c926",
+ "0xf720431e778c20adee2bcb535f1cfc79d3f48a40baa81fdbf05c7449bd356370",
+ "0x0a9ed816bf71cb78ee57342ca8e69fefa8e66a9b2579168c6b86de7eedaeadc8",
+ "0x7fac6ec822a5943c4d04f74a1e637293c824ddef487582ee53250957afa40ae5"
+ ]
+}
\ No newline at end of file
diff --git a/contracts/test/tokenBridge/BridgedToken.ts b/contracts/test/tokenBridge/BridgedToken.ts
new file mode 100644
index 000000000..5ab9536ed
--- /dev/null
+++ b/contracts/test/tokenBridge/BridgedToken.ts
@@ -0,0 +1,123 @@
+import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
+import { expect } from "chai";
+import { ethers, upgrades } from "hardhat";
+
+const initialUserBalance = 10000;
+
+async function createTokenBeaconProxy() {
+ const [admin, unknown] = await ethers.getSigners();
+
+ const BridgedToken = await ethers.getContractFactory("BridgedToken");
+
+ // Deploy token beacon
+ const l1TokenBeacon = await upgrades.deployBeacon(BridgedToken);
+ await l1TokenBeacon.deployed();
+
+ const l2TokenBeacon = await upgrades.deployBeacon(BridgedToken);
+ await l2TokenBeacon.deployed();
+
+ // Create tokens
+ const abcToken = await upgrades.deployBeaconProxy(l1TokenBeacon.address, BridgedToken, ["AbcToken", "ABC", 18]);
+
+ const sixDecimalsToken = await upgrades.deployBeaconProxy(l1TokenBeacon.address, BridgedToken, [
+ "sixDecimalsToken",
+ "SIX",
+ 6,
+ ]);
+
+ // Create a new token implementation
+ const UpgradedBridgedToken = await ethers.getContractFactory("UpgradedBridgedToken");
+ const newImplementation = await UpgradedBridgedToken.deploy();
+ await newImplementation.deployed();
+
+ // Update l2TokenBeacon with new implementation
+ await l2TokenBeacon.connect(admin).upgradeTo(newImplementation.address);
+
+ // Set initial balance
+ await sixDecimalsToken.connect(admin).mint(unknown.address, initialUserBalance);
+
+ return {
+ admin,
+ unknown,
+ l1TokenBeacon,
+ l2TokenBeacon,
+ newImplementation,
+ UpgradedBridgedToken,
+ abcToken,
+ sixDecimalsToken,
+ };
+}
+
+describe("BridgedToken", function () {
+ it("Should deploy BridgedToken", async function () {
+ const { abcToken, sixDecimalsToken } = await loadFixture(createTokenBeaconProxy);
+ expect(abcToken.address).to.be.not.null;
+ expect(sixDecimalsToken.address).to.be.not.null;
+ });
+
+ it("Should set the right metadata", async function () {
+ const { abcToken, sixDecimalsToken } = await loadFixture(createTokenBeaconProxy);
+ expect(await abcToken.name()).to.be.equal("AbcToken");
+ expect(await abcToken.symbol()).to.be.equal("ABC");
+ expect(await abcToken.decimals()).to.be.equal(18);
+ expect(await sixDecimalsToken.name()).to.be.equal("sixDecimalsToken");
+ expect(await sixDecimalsToken.symbol()).to.be.equal("SIX");
+ expect(await sixDecimalsToken.decimals()).to.be.equal(6);
+ });
+
+ it("Should mint tokens", async function () {
+ const { admin, unknown, abcToken } = await loadFixture(createTokenBeaconProxy);
+ const amount = 100;
+ await abcToken.connect(admin).mint(unknown.address, amount);
+ expect(await abcToken.balanceOf(unknown.address)).to.be.equal(amount);
+ });
+
+ it("Should burn tokens", async function () {
+ const { admin, unknown, sixDecimalsToken } = await loadFixture(createTokenBeaconProxy);
+ const amount = 100;
+ await sixDecimalsToken.connect(unknown).approve(admin.address, amount);
+ await sixDecimalsToken.connect(admin).burn(unknown.address, amount);
+ expect(await sixDecimalsToken.balanceOf(unknown.address)).to.be.equal(initialUserBalance - amount);
+ });
+
+ it("Should revert if mint/burn are called by an unknown address", async function () {
+ const { unknown, abcToken } = await loadFixture(createTokenBeaconProxy);
+ const amount = 100;
+ await expect(abcToken.connect(unknown).mint(unknown.address, amount)).to.be.revertedWithCustomError(
+ abcToken,
+ "OnlyBridge",
+ );
+ await expect(abcToken.connect(unknown).burn(unknown.address, amount)).to.be.revertedWithCustomError(
+ abcToken,
+ "OnlyBridge",
+ );
+ });
+});
+
+describe("BeaconProxy", function () {
+ it("Should enable upgrade of existing beacon proxy", async function () {
+ const { admin, l1TokenBeacon, abcToken, newImplementation, UpgradedBridgedToken } = await loadFixture(
+ createTokenBeaconProxy,
+ );
+ await l1TokenBeacon.connect(admin).upgradeTo(newImplementation.address);
+ expect(await l1TokenBeacon.implementation()).to.be.equal(newImplementation.address);
+ expect(await UpgradedBridgedToken.attach(abcToken.address).isUpgraded()).to.be.equal(true);
+ });
+
+ it("Should deploy new beacon proxy with the updated implementation", async function () {
+ const { l2TokenBeacon, UpgradedBridgedToken } = await loadFixture(createTokenBeaconProxy);
+ const newTokenBeaconProxy = await upgrades.deployBeaconProxy(l2TokenBeacon.address, UpgradedBridgedToken, [
+ "NAME",
+ "SYMBOL",
+ 18, // Decimals
+ ]);
+ expect(await newTokenBeaconProxy.isUpgraded()).to.be.equal(true);
+ });
+
+ it("Beacon upgrade should only be done by the owner", async function () {
+ const { unknown, l1TokenBeacon, newImplementation } = await loadFixture(createTokenBeaconProxy);
+ await expect(l1TokenBeacon.connect(unknown).upgradeTo(newImplementation.address)).to.be.revertedWith(
+ "Ownable: caller is not the owner",
+ );
+ });
+});
diff --git a/contracts/test/tokenBridge/E2E.ts b/contracts/test/tokenBridge/E2E.ts
new file mode 100644
index 000000000..854d497a0
--- /dev/null
+++ b/contracts/test/tokenBridge/E2E.ts
@@ -0,0 +1,566 @@
+import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
+import { expect } from "chai";
+import { BigNumber } from "ethers";
+import { ethers } from "hardhat";
+
+import { deployTokenBridgeWithMockMessaging } from "../../scripts/tokenBridge/test/deployTokenBridges";
+import { deployTokens } from "../../scripts/tokenBridge/test/deployTokens";
+
+const initialUserBalance = BigNumber.from(10 ** 9);
+const RESERVED_STATUS = ethers.utils.getAddress("0x0000000000000000000000000000000000000111");
+const NATIVE_STATUS = ethers.utils.getAddress("0x0000000000000000000000000000000000000222");
+const DEPLOYED_STATUS = ethers.utils.getAddress("0x0000000000000000000000000000000000000333");
+const mockName = "L1 DAI";
+const mockSymbol = "L1DAI";
+const mockDecimals = 18;
+
+describe("E2E tests", function () {
+ async function deployContractsFixture() {
+ const [deployer, user] = await ethers.getSigners();
+
+ // Deploy and configure bridges
+ const deploymentFixture = await deployTokenBridgeWithMockMessaging();
+
+ // Deploy tokens
+ const tokens = await deployTokens();
+
+ // Mint tokens for user and approve bridge
+ for (const name in tokens) {
+ const token = tokens[name];
+ await token.mint(user.address, initialUserBalance);
+
+ let bridgeAddress;
+ if ((await token.name()).includes("L1")) {
+ bridgeAddress = deploymentFixture.l1TokenBridge.address;
+ }
+ if ((await token.name()).includes("L2")) {
+ bridgeAddress = deploymentFixture.l2TokenBridge.address;
+ }
+
+ await token.connect(user).approve(bridgeAddress, ethers.constants.MaxUint256);
+ }
+
+ const encodedData = ethers.utils.defaultAbiCoder.encode(
+ ["string", "string", "uint8"],
+ [mockName, mockSymbol, mockDecimals],
+ );
+
+ return { deployer, user, ...deploymentFixture, tokens, encodedData };
+ }
+
+ describe("Bridging", function () {
+ it("Should have the correct balance and totalSupply on both chains after bridging", async function () {
+ const {
+ user,
+ l1TokenBridge,
+ l2TokenBridge,
+ tokens: { L1DAI },
+ chainIds,
+ } = await loadFixture(deployContractsFixture);
+ const bridgeAmount = 100;
+
+ await l1TokenBridge.connect(user).bridgeToken(L1DAI.address, bridgeAmount, user.address);
+
+ const userBalance = initialUserBalance.toNumber() - bridgeAmount;
+ expect(await L1DAI.balanceOf(user.address)).to.be.equal(userBalance);
+ expect(await L1DAI.balanceOf(l1TokenBridge.address)).to.be.equal(bridgeAmount);
+
+ const l2TokenAddress = await l2TokenBridge.nativeToBridgedToken(chainIds[0], L1DAI.address);
+ const BridgedToken = await ethers.getContractFactory("BridgedToken");
+ const l2Token = BridgedToken.attach(l2TokenAddress);
+
+ expect(await l2Token.balanceOf(user.address)).to.be.equal(bridgeAmount);
+ expect(await l2Token.totalSupply()).to.be.equal(bridgeAmount);
+ });
+
+ it("Should have the correct balance and totalSupply on both chains after back and forth bridging", async function () {
+ const {
+ user,
+ l1TokenBridge,
+ l2TokenBridge,
+ tokens: { L1DAI },
+ chainIds,
+ } = await loadFixture(deployContractsFixture);
+ const firstBridgeAmount = 100;
+ const secondBridgeAmount = 30;
+ const netBridgedAmount = firstBridgeAmount - secondBridgeAmount;
+
+ await l1TokenBridge.connect(user).bridgeToken(L1DAI.address, firstBridgeAmount, user.address);
+
+ const l2TokenAddress = await l2TokenBridge.nativeToBridgedToken(chainIds[0], L1DAI.address);
+ const BridgedToken = await ethers.getContractFactory("BridgedToken");
+ const l2Token = BridgedToken.attach(l2TokenAddress);
+
+ expect(await l2Token.allowance(user.address, l2TokenBridge.address)).to.be.equal(0);
+ await expect(
+ l2TokenBridge.connect(user).bridgeToken(l2TokenAddress, secondBridgeAmount, user.address),
+ ).to.be.revertedWith("ERC20: insufficient allowance");
+
+ await l2Token.connect(user).approve(l2TokenBridge.address, secondBridgeAmount);
+ await l2TokenBridge.connect(user).bridgeToken(l2TokenAddress, secondBridgeAmount, user.address);
+
+ const userBalance = initialUserBalance.toNumber() - netBridgedAmount;
+ expect(await L1DAI.balanceOf(user.address)).to.be.equal(userBalance);
+ expect(await L1DAI.balanceOf(l1TokenBridge.address)).to.be.equal(netBridgedAmount);
+ expect(await l2Token.balanceOf(user.address)).to.be.equal(netBridgedAmount);
+ expect(await l2Token.totalSupply()).to.be.equal(netBridgedAmount);
+ });
+
+ it("Should support fee tokens and set balances correctly", async function () {
+ const { user, l1TokenBridge, l2TokenBridge, chainIds } = await loadFixture(deployContractsFixture);
+
+ const ERC20 = await ethers.getContractFactory("ERC20Fees");
+
+ const feeToken = await ERC20.deploy("Token with fee", "FEE", 200); // 2% fee
+ await feeToken.deployed();
+
+ // Mint tokens for user and approve bridge
+ await feeToken.mint(user.address, initialUserBalance);
+ await feeToken.connect(user).approve(l1TokenBridge.address, ethers.constants.MaxUint256);
+
+ // Transfer some tokens from the user to the contract
+ const amountToTransfer = BigNumber.from(100);
+ await l1TokenBridge.connect(user).bridgeToken(feeToken.address, amountToTransfer, user.address);
+
+ // Calculate actual transfered amount
+ const burnAmount = amountToTransfer.mul(2).div(100); // 2% fee
+ const transferredAmount = amountToTransfer.sub(burnAmount);
+
+ // Get bridged token address
+ const l2FeeTokenAddress = await l2TokenBridge.nativeToBridgedToken(chainIds[0], feeToken.address);
+ const l2FeeToken = ERC20.attach(l2FeeTokenAddress);
+
+ // Check balances and total supply
+ expect(await feeToken.balanceOf(user.address)).to.be.equal(initialUserBalance.sub(amountToTransfer));
+ expect(await feeToken.balanceOf(l1TokenBridge.address)).to.be.equal(transferredAmount);
+ expect(await l2FeeToken.balanceOf(user.address)).to.be.equal(transferredAmount);
+ expect(await l2FeeToken.totalSupply()).to.be.equal(transferredAmount);
+ });
+
+ it("Should not be able to bridge 0 tokens", async function () {
+ const {
+ user,
+ l1TokenBridge,
+ tokens: { L1DAI },
+ } = await loadFixture(deployContractsFixture);
+ const bridgeAmount = 0;
+
+ await expect(
+ l1TokenBridge.connect(user).bridgeToken(L1DAI.address, bridgeAmount, user.address),
+ ).to.be.revertedWithCustomError(l1TokenBridge, "ZeroAmountNotAllowed");
+ });
+
+ it("Should not be able to bridge if token is the 0 address", async function () {
+ const { user, l1TokenBridge } = await loadFixture(deployContractsFixture);
+ const bridgeAmount = 10;
+
+ await expect(
+ l1TokenBridge.connect(user).bridgeToken(ethers.constants.AddressZero, bridgeAmount, user.address),
+ ).to.be.revertedWithCustomError(l1TokenBridge, "ZeroAddressNotAllowed");
+ });
+
+ it("Should create bridged token on the targeted layer even if both native tokens on both layers have the same address and are bridged at the same time", async function () {
+ const {
+ user,
+ l1TokenBridge,
+ l2TokenBridge,
+ messageService,
+ tokens: { L1DAI },
+ chainIds,
+ } = await loadFixture(deployContractsFixture);
+ const balanceTokenUser = await L1DAI.balanceOf(user.address);
+ const bridgeAmount = 10;
+ const MockERC20 = await ethers.getContractFactory("MockERC20MintBurn");
+
+ // Deploy another message service that will not send the message along to the other
+ // layer so that we can simulate the same state we could get if 2 tokens are sent
+ // at the same time, meaning that the function bridgeToken() has been called
+ // on both layers while the completeBridging has not been called yet
+ const MockMessageServiceV2 = await ethers.getContractFactory("MockMessageServiceV2");
+ const mockMessageServiceV2 = await MockMessageServiceV2.deploy();
+ await mockMessageServiceV2.deployed();
+
+ await L1DAI.connect(user).approve(l2TokenBridge.address, ethers.constants.MaxUint256);
+ await l2TokenBridge.setMessageService(mockMessageServiceV2.address);
+ await l2TokenBridge.connect(user).bridgeToken(L1DAI.address, bridgeAmount, user.address);
+
+ expect(await l2TokenBridge.nativeToBridgedToken(chainIds[1], L1DAI.address)).to.be.equal(NATIVE_STATUS);
+
+ await l1TokenBridge.setMessageService(mockMessageServiceV2.address);
+ await l1TokenBridge.connect(user).bridgeToken(L1DAI.address, bridgeAmount, user.address);
+
+ expect(await l1TokenBridge.nativeToBridgedToken(chainIds[0], L1DAI.address)).to.be.equal(NATIVE_STATUS);
+
+ // We check that the brigedToken have not been created yet
+ expect(await l2TokenBridge.nativeToBridgedToken(chainIds[0], L1DAI.address)).to.be.equal(
+ ethers.constants.AddressZero,
+ );
+ expect(await l1TokenBridge.nativeToBridgedToken(chainIds[1], L1DAI.address)).to.be.equal(
+ ethers.constants.AddressZero,
+ );
+
+ // Here we are in the situation where both tokens are native to the both chains but neither
+ // of them have arrived to their destination yet, completeBridging() has not been called yet
+
+ // We reassign the normal message service
+ await l2TokenBridge.setMessageService(messageService.address);
+ await l1TokenBridge.setMessageService(messageService.address);
+
+ // This initial amount has to be ignored for the test since in reality
+ // the first message would go through the message service and funds would not be stuck
+ const initialBalanceTokenL1Bridge = await L1DAI.balanceOf(l1TokenBridge.address);
+ const initialBalanceTokenL2Bridge = await L1DAI.balanceOf(l2TokenBridge.address);
+
+ expect(initialBalanceTokenL1Bridge).to.be.equal(bridgeAmount);
+ expect(initialBalanceTokenL2Bridge).to.be.equal(bridgeAmount);
+
+ // Now we try to bridge with the right message service
+ // It should create the bridgedToken on the other layer for each native token
+ await l2TokenBridge.connect(user).bridgeToken(L1DAI.address, bridgeAmount, user.address);
+
+ // BridgedToken on L1 is created
+ const bridgedTokenL1Addr = await l1TokenBridge.nativeToBridgedToken(chainIds[1], L1DAI.address);
+ expect(bridgedTokenL1Addr).to.not.equal(ethers.constants.AddressZero);
+ const bridgedTokenL1 = MockERC20.attach(bridgedTokenL1Addr);
+ expect(await L1DAI.balanceOf(l2TokenBridge.address)).to.be.equal(bridgeAmount * 2);
+ expect(await bridgedTokenL1.balanceOf(user.address)).to.be.equal(bridgeAmount);
+
+ await l1TokenBridge.connect(user).bridgeToken(L1DAI.address, bridgeAmount, user.address);
+
+ // BridgedToken on L2 is created
+ const bridgedTokenL2Addr = await l2TokenBridge.nativeToBridgedToken(chainIds[0], L1DAI.address);
+ expect(bridgedTokenL2Addr).to.not.equal(ethers.constants.AddressZero);
+ const bridgedTokenL2 = MockERC20.attach(bridgedTokenL2Addr);
+ expect(await L1DAI.balanceOf(l1TokenBridge.address)).to.be.equal(bridgeAmount * 2);
+ expect(await bridgedTokenL2.balanceOf(user.address)).to.be.equal(bridgeAmount);
+
+ // At this point the user bridged DAI 4 times so he should have 4 times
+ // the bridge amount less in his balance
+ expect(await L1DAI.balanceOf(user.address)).to.be.equal(balanceTokenUser - bridgeAmount * 4);
+
+ // The user has received the minted bridgedToken on both chains
+ // Now we get back our native tokens on both chains by bridging the bridged tokens that the user has received
+ await bridgedTokenL1.connect(user).approve(l1TokenBridge.address, ethers.constants.MaxUint256);
+ await bridgedTokenL2.connect(user).approve(l2TokenBridge.address, ethers.constants.MaxUint256);
+
+ await l1TokenBridge.connect(user).bridgeToken(bridgedTokenL1.address, bridgeAmount, user.address);
+ await l2TokenBridge.connect(user).bridgeToken(bridgedTokenL2.address, bridgeAmount, user.address);
+
+ expect(await bridgedTokenL1.balanceOf(user.address)).to.be.equal(0);
+ expect(await bridgedTokenL2.balanceOf(user.address)).to.be.equal(0);
+ expect(await L1DAI.balanceOf(user.address)).to.be.equal(balanceTokenUser - bridgeAmount * 2);
+ });
+
+ describe("BridgedToken deployment", function () {
+ it("Should return NO_NAME and NO_SYMBOL when the token does not have them.", async function () {
+ const { user, l1TokenBridge, l2TokenBridge, chainIds } = await loadFixture(deployContractsFixture);
+
+ const ERC20 = await ethers.getContractFactory("MockERC20NoNameMintBurn");
+ const noNameToken = await ERC20.deploy();
+ await noNameToken.mint(user.address, initialUserBalance);
+ await noNameToken.connect(user).approve(l1TokenBridge.address, ethers.constants.MaxUint256);
+
+ const bridgeAmount = 100;
+
+ await l1TokenBridge.connect(user).bridgeToken(noNameToken.address, bridgeAmount, user.address);
+
+ const l2TokenAddress = await l2TokenBridge.nativeToBridgedToken(chainIds[0], noNameToken.address);
+ const BridgedToken = await ethers.getContractFactory("BridgedToken");
+ const l2Token = BridgedToken.attach(l2TokenAddress);
+
+ await l2Token.connect(user).approve(l2TokenBridge.address, bridgeAmount);
+ await l2TokenBridge.connect(user).bridgeToken(l2TokenAddress, bridgeAmount, user.address);
+
+ expect(await l2Token.name()).to.be.equal("NO_NAME");
+ expect(await l2Token.symbol()).to.be.equal("NO_SYMBOL");
+ });
+ it("Should return UNKNOWN or create a byte array for weird name and symbol.", async function () {
+ const { user, l1TokenBridge, l2TokenBridge, chainIds } = await loadFixture(deployContractsFixture);
+
+ const ERC20 = await ethers.getContractFactory("MockERC20WeirdNameSymbol");
+ const noNameToken = await ERC20.deploy();
+ await noNameToken.mint(user.address, initialUserBalance);
+ await noNameToken.connect(user).approve(l1TokenBridge.address, ethers.constants.MaxUint256);
+
+ const bridgeAmount = 100;
+
+ await l1TokenBridge.connect(user).bridgeToken(noNameToken.address, bridgeAmount, user.address);
+
+ const l2TokenAddress = await l2TokenBridge.nativeToBridgedToken(chainIds[0], noNameToken.address);
+ const BridgedToken = await ethers.getContractFactory("BridgedToken");
+ const l2Token = BridgedToken.attach(l2TokenAddress);
+
+ await l2Token.connect(user).approve(l2TokenBridge.address, bridgeAmount);
+ await l2TokenBridge.connect(user).bridgeToken(l2TokenAddress, bridgeAmount, user.address);
+
+ expect(await l2Token.name()).to.be.equal("NOT_VALID_ENCODING");
+ expect(await l2Token.symbol()).to.be.equal("\u0001");
+ });
+
+ it("Should not revert if a token being bridged exists as a native token on the other layer", async function () {
+ const {
+ user,
+ l1TokenBridge,
+ l2TokenBridge,
+ tokens: { L1DAI },
+ } = await loadFixture(deployContractsFixture);
+
+ const bridgeAmount = 100;
+ await l1TokenBridge.connect(user).bridgeToken(L1DAI.address, bridgeAmount, user.address);
+
+ await L1DAI.connect(user).approve(l2TokenBridge.address, bridgeAmount);
+ await expect(l2TokenBridge.connect(user).bridgeToken(L1DAI.address, bridgeAmount, user.address)).to.not.be
+ .reverted;
+ });
+ });
+ });
+
+ describe("setCustomContract", function () {
+ it("Should mint and burn correctly from a target contract", async function () {
+ const {
+ user,
+ l1TokenBridge,
+ l2TokenBridge,
+ tokens: { L1USDT },
+ } = await loadFixture(deployContractsFixture);
+ const amountBridged = 100;
+ const amountBridgedBack = 100;
+
+ // Deploy custom contract
+ const CustomContract = await ethers.getContractFactory("MockERC20MintBurn");
+ const customContract = await CustomContract.deploy("CustomContract", "CC");
+
+ // Set custom contract
+ await l2TokenBridge.setCustomContract(L1USDT.address, customContract.address);
+
+ // Bridge token (allowance has been set in the fixture)
+ await l1TokenBridge.connect(user).bridgeToken(L1USDT.address, amountBridged, user.address);
+
+ expect(await L1USDT.balanceOf(l1TokenBridge.address)).to.be.equal(amountBridged);
+ expect(await customContract.balanceOf(user.address)).to.be.equal(amountBridged);
+ expect(await customContract.totalSupply()).to.be.equal(amountBridged);
+
+ // Bridge back
+ const USDTBalanceBefore = await L1USDT.balanceOf(user.address);
+ await l2TokenBridge.connect(user).bridgeToken(customContract.address, amountBridgedBack, user.address);
+ const USDTBalanceAfter = await L1USDT.balanceOf(user.address);
+
+ expect(await customContract.balanceOf(user.address)).to.be.equal(amountBridged - amountBridgedBack);
+ expect(await customContract.totalSupply()).to.be.equal(amountBridged - amountBridgedBack);
+ expect(USDTBalanceAfter - USDTBalanceBefore).to.be.equal(amountBridgedBack);
+ });
+
+ it("Should not be able to set a custom contract that is already a bridged token contract or one of the STATUS", async function () {
+ const {
+ user,
+ l1TokenBridge,
+ l2TokenBridge,
+ tokens: { L1USDT, L2UNI },
+ chainIds,
+ } = await loadFixture(deployContractsFixture);
+ const amountBridged = 100;
+
+ await l1TokenBridge.connect(user).bridgeToken(L1USDT.address, amountBridged, user.address);
+
+ const brigedToNativeContract = await l2TokenBridge.nativeToBridgedToken(chainIds[0], L1USDT.address);
+
+ // Try to set custom contract with a bridgedTokenContract, should revert
+ await expect(
+ l2TokenBridge.setCustomContract(L2UNI.address, brigedToNativeContract),
+ ).to.be.revertedWithCustomError(l2TokenBridge, "AlreadyBrigedToNativeTokenSet");
+
+ await expect(l2TokenBridge.setCustomContract(L2UNI.address, RESERVED_STATUS)).to.be.revertedWithCustomError(
+ l2TokenBridge,
+ "StatusAddressNotAllowed",
+ );
+
+ await expect(l2TokenBridge.setCustomContract(L2UNI.address, NATIVE_STATUS)).to.be.revertedWithCustomError(
+ l2TokenBridge,
+ "StatusAddressNotAllowed",
+ );
+
+ await expect(l2TokenBridge.setCustomContract(L2UNI.address, DEPLOYED_STATUS)).to.be.revertedWithCustomError(
+ l2TokenBridge,
+ "StatusAddressNotAllowed",
+ );
+ });
+ });
+
+ describe("Reserved tokens", function () {
+ it("Should be possible for the admin to reserve a token", async function () {
+ const {
+ deployer,
+ l1TokenBridge,
+ tokens: { L1DAI },
+ chainIds,
+ } = await loadFixture(deployContractsFixture);
+ await expect(l1TokenBridge.connect(deployer).setReserved(L1DAI.address)).not.to.be.revertedWith(
+ "TokenBridge: token already bridged",
+ );
+ expect(await l1TokenBridge.nativeToBridgedToken(chainIds[0], L1DAI.address)).to.be.equal(RESERVED_STATUS);
+ });
+
+ it("Should not be possible to bridge reserved tokens", async function () {
+ const {
+ deployer,
+ user,
+ l1TokenBridge,
+ tokens: { L1DAI },
+ } = await loadFixture(deployContractsFixture);
+ await l1TokenBridge.connect(deployer).setReserved(L1DAI.address);
+ await expect(
+ l1TokenBridge.connect(user).bridgeToken(L1DAI.address, 1, user.address),
+ ).to.be.revertedWithCustomError(l1TokenBridge, "ReservedToken");
+ });
+
+ it("Should only be possible to reserve a token if it has not been bridged before", async function () {
+ const {
+ deployer,
+ user,
+ l1TokenBridge,
+ tokens: { L1DAI },
+ } = await loadFixture(deployContractsFixture);
+ l1TokenBridge.connect(user).bridgeToken(L1DAI.address, 1, user.address);
+ await expect(l1TokenBridge.connect(deployer).setReserved(L1DAI.address)).to.be.revertedWithCustomError(
+ l1TokenBridge,
+ "AlreadyBridgedToken",
+ );
+ });
+ });
+
+ describe("Modifying Message Service", function () {
+ it("Should be able to modify the Message Service and bridge properly", async function () {
+ const {
+ deployer,
+ user,
+ l1TokenBridge,
+ l2TokenBridge,
+ tokens: { L1DAI },
+ chainIds,
+ } = await loadFixture(deployContractsFixture);
+ // Deploy new Message Service
+ const MessageServiceFactory = await ethers.getContractFactory("MockMessageService");
+ const messageService = await MessageServiceFactory.deploy();
+ await messageService.deployed();
+
+ await l1TokenBridge.connect(deployer).setMessageService(messageService.address);
+ await l2TokenBridge.connect(deployer).setMessageService(messageService.address);
+
+ const bridgeAmount = 100;
+
+ await l1TokenBridge.connect(user).bridgeToken(L1DAI.address, bridgeAmount, user.address);
+
+ const userBalance = initialUserBalance.toNumber() - bridgeAmount;
+ expect(await L1DAI.balanceOf(user.address)).to.be.equal(userBalance);
+ expect(await L1DAI.balanceOf(l1TokenBridge.address)).to.be.equal(bridgeAmount);
+
+ const l2TokenAddress = await l2TokenBridge.nativeToBridgedToken(chainIds[0], L1DAI.address);
+ const BridgedToken = await ethers.getContractFactory("BridgedToken");
+ const l2Token = BridgedToken.attach(l2TokenAddress);
+
+ expect(await l2Token.balanceOf(user.address)).to.be.equal(bridgeAmount);
+ expect(await l2Token.totalSupply()).to.be.equal(bridgeAmount);
+ });
+
+ it("Should no be able to set Message Service by non-owner caller", async function () {
+ const { user, l1TokenBridge } = await loadFixture(deployContractsFixture);
+ // Deploy new Message Service
+ const MessageServiceFactory = await ethers.getContractFactory("MockMessageService");
+ const messageService = await MessageServiceFactory.deploy();
+ await messageService.deployed();
+
+ await expect(l1TokenBridge.connect(user).setMessageService(messageService.address)).to.be.revertedWith(
+ "Ownable: caller is not the owner",
+ );
+ });
+
+ it("Should emit en event when updating the Message Service", async function () {
+ const { l1TokenBridge, deployer } = await loadFixture(deployContractsFixture);
+ // Deploy new Message Service
+ const MessageServiceFactory = await ethers.getContractFactory("MockMessageService");
+ const messageService = await MessageServiceFactory.deploy();
+ await messageService.deployed();
+
+ const oldMessageService = await l1TokenBridge.messageService();
+ await expect(l1TokenBridge.setMessageService(messageService.address))
+ .to.emit(l1TokenBridge, "MessageServiceUpdated")
+ .withArgs(messageService.address, oldMessageService, deployer.address);
+ });
+ });
+
+ describe("Token deployment status", function () {
+ it("Should set correct deployment status on both chain", async function () {
+ const {
+ user,
+ l1TokenBridge,
+ l2TokenBridge,
+ tokens: { L1DAI, L1USDT },
+ chainIds,
+ } = await loadFixture(deployContractsFixture);
+
+ const bridgeAmount = 100;
+ await l1TokenBridge.connect(user).bridgeToken(L1DAI.address, bridgeAmount, user.address);
+ await l1TokenBridge.connect(user).bridgeToken(L1USDT.address, bridgeAmount, user.address);
+
+ expect(await l1TokenBridge.nativeToBridgedToken(chainIds[0], L1DAI.address)).to.be.equal(NATIVE_STATUS);
+ expect(await l1TokenBridge.nativeToBridgedToken(chainIds[0], L1USDT.address)).to.be.equal(NATIVE_STATUS);
+
+ const L2DAIBridgedAddress = await l2TokenBridge.nativeToBridgedToken(chainIds[0], L1DAI.address);
+ const L2USDTBridgedAddress = await l2TokenBridge.nativeToBridgedToken(chainIds[0], L1USDT.address);
+
+ await l2TokenBridge.confirmDeployment([L2DAIBridgedAddress, L2USDTBridgedAddress]);
+
+ expect(await l1TokenBridge.nativeToBridgedToken(chainIds[0], L1DAI.address)).to.be.equal(DEPLOYED_STATUS);
+ expect(await l1TokenBridge.nativeToBridgedToken(chainIds[0], L1USDT.address)).to.be.equal(DEPLOYED_STATUS);
+
+ // Should not revert
+ await expect(l1TokenBridge.connect(user).bridgeToken(L1DAI.address, bridgeAmount, user.address)).to.be.not
+ .reverted;
+
+ // @TODO: check metadata are not sent for new L1DAI tx
+ });
+
+ it("Should revert when trying to change the status of a token that has not been deployed yet", async function () {
+ const {
+ user,
+ l1TokenBridge,
+ l2TokenBridge,
+ tokens: { L1DAI, L1USDT },
+ chainIds,
+ } = await loadFixture(deployContractsFixture);
+
+ const bridgeAmount = 100;
+ await l1TokenBridge.connect(user).bridgeToken(L1DAI.address, bridgeAmount, user.address);
+
+ const L2DAIBridgedAddress = await l2TokenBridge.nativeToBridgedToken(chainIds[0], L1DAI.address);
+ await expect(
+ l2TokenBridge.confirmDeployment([L2DAIBridgedAddress, L1USDT.address]),
+ ).to.be.revertedWithCustomError(l2TokenBridge, "TokenNotDeployed");
+ });
+
+ it("Should be able to bridge back to the original layer if that token is marked as DEPLOYED", async function () {
+ const {
+ user,
+ l1TokenBridge,
+ l2TokenBridge,
+ tokens: { L1DAI },
+ chainIds,
+ } = await loadFixture(deployContractsFixture);
+ const bridgeAmount = 10;
+
+ const initialAmount = await L1DAI.balanceOf(user.address);
+ await l1TokenBridge.connect(user).bridgeToken(L1DAI.address, bridgeAmount, user.address);
+ const L2DAIBridgedAddress = await l2TokenBridge.nativeToBridgedToken(chainIds[0], L1DAI.address);
+ await l2TokenBridge.confirmDeployment([L2DAIBridgedAddress]);
+
+ expect(await L1DAI.balanceOf(user.address)).to.be.equal(initialAmount.sub(bridgeAmount));
+ expect(await L1DAI.attach(L2DAIBridgedAddress).balanceOf(user.address)).to.be.equal(bridgeAmount);
+ expect(await l1TokenBridge.nativeToBridgedToken(chainIds[0], L1DAI.address)).to.be.equal(DEPLOYED_STATUS);
+
+ await L1DAI.attach(L2DAIBridgedAddress).connect(user).approve(l2TokenBridge.address, bridgeAmount);
+ await l2TokenBridge.connect(user).bridgeToken(L2DAIBridgedAddress, bridgeAmount, user.address);
+
+ expect(await L1DAI.balanceOf(user.address)).to.be.equal(initialAmount);
+ });
+ });
+});
diff --git a/contracts/test/tokenBridge/TokenBridge.ts b/contracts/test/tokenBridge/TokenBridge.ts
new file mode 100644
index 000000000..3559369ed
--- /dev/null
+++ b/contracts/test/tokenBridge/TokenBridge.ts
@@ -0,0 +1,587 @@
+import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
+import { expect } from "chai";
+import { BigNumber } from "ethers";
+import { ethers, upgrades } from "hardhat";
+import { deployTokenBridgeWithMockMessaging } from "../../scripts/tokenBridge/test/deployTokenBridges";
+import { deployTokens } from "../../scripts/tokenBridge/test/deployTokens";
+import { getPermitData } from "./utils/permitHelper";
+
+const initialUserBalance = BigNumber.from(10 ** 9);
+const mockName = "L1 DAI";
+const mockSymbol = "L1DAI";
+const mockDecimals = 18;
+const RESERVED_STATUS = ethers.utils.getAddress("0x0000000000000000000000000000000000000111");
+const PLACEHOLDER_ADDRESS = ethers.utils.getAddress("0x5555555555555555555555555555555555555555");
+const CUSTOM_ADDRESS = ethers.utils.getAddress("0x9999999999999999999999999999999999999999");
+const EMPTY_PERMIT_DATA = "0x";
+
+describe("TokenBridge", function () {
+ async function deployContractsFixture() {
+ const [owner, user] = await ethers.getSigners();
+
+ // Deploy and configure bridges
+ const deploymentFixture = await deployTokenBridgeWithMockMessaging();
+
+ // Deploy tokens
+ const tokens = await deployTokens();
+
+ // Mint tokens for user and approve bridge
+ for (const name in tokens) {
+ const token = tokens[name];
+ await token.mint(user.address, initialUserBalance);
+
+ let bridgeAddress;
+ if ((await token.name()).includes("L1")) {
+ bridgeAddress = deploymentFixture.l1TokenBridge.address;
+ }
+ if ((await token.name()).includes("L2")) {
+ bridgeAddress = deploymentFixture.l2TokenBridge.address;
+ }
+
+ await token.connect(user).approve(bridgeAddress, ethers.constants.MaxUint256);
+ }
+ const encodedTokenMetadata = ethers.utils.defaultAbiCoder.encode(
+ ["string", "string", "uint8"],
+ [mockName, mockSymbol, mockDecimals],
+ );
+ return { owner, user, ...deploymentFixture, tokens, encodedTokenMetadata };
+ }
+
+ describe("initialize", function () {
+ it("Should revert if it has already been intialized", async function () {
+ const { user, l1TokenBridge, chainIds } = await loadFixture(deployContractsFixture);
+ await expect(
+ l1TokenBridge
+ .connect(user)
+ .initialize(PLACEHOLDER_ADDRESS, PLACEHOLDER_ADDRESS, PLACEHOLDER_ADDRESS, chainIds[0], chainIds[1], []),
+ ).to.be.revertedWith("Initializable: contract is already initialized");
+ });
+
+ it("Should revert if one of the initializing parameters is address 0", async function () {
+ const { l1TokenBridge, chainIds } = await loadFixture(deployContractsFixture);
+ const TokenBridge = await ethers.getContractFactory("TokenBridge");
+ await expect(
+ upgrades.deployProxy(TokenBridge, [
+ ethers.constants.AddressZero,
+ PLACEHOLDER_ADDRESS,
+ PLACEHOLDER_ADDRESS,
+ chainIds[0],
+ chainIds[1],
+ [],
+ ]),
+ ).to.be.revertedWithCustomError(l1TokenBridge, "ZeroAddressNotAllowed");
+
+ await expect(
+ upgrades.deployProxy(TokenBridge, [
+ PLACEHOLDER_ADDRESS,
+ ethers.constants.AddressZero,
+ PLACEHOLDER_ADDRESS,
+ chainIds[0],
+ chainIds[1],
+ [],
+ ]),
+ ).to.be.revertedWithCustomError(l1TokenBridge, "ZeroAddressNotAllowed");
+
+ await expect(
+ upgrades.deployProxy(TokenBridge, [
+ PLACEHOLDER_ADDRESS,
+ PLACEHOLDER_ADDRESS,
+ ethers.constants.AddressZero,
+ chainIds[0],
+ chainIds[1],
+ [],
+ ]),
+ ).to.be.revertedWithCustomError(l1TokenBridge, "ZeroAddressNotAllowed");
+
+ await expect(
+ upgrades.deployProxy(TokenBridge, [
+ PLACEHOLDER_ADDRESS,
+ PLACEHOLDER_ADDRESS,
+ PLACEHOLDER_ADDRESS,
+ chainIds[0],
+ chainIds[1],
+ [PLACEHOLDER_ADDRESS, ethers.constants.AddressZero],
+ ]),
+ ).to.be.revertedWithCustomError(l1TokenBridge, "ZeroAddressNotAllowed");
+ });
+ });
+
+ describe("Permissions", function () {
+ it("Should revert if completeBridging is not called by the messageService", async function () {
+ const {
+ user,
+ l1TokenBridge,
+ tokens: { L1DAI },
+ encodedTokenMetadata,
+ chainIds,
+ } = await loadFixture(deployContractsFixture);
+ await expect(
+ l1TokenBridge.connect(user).completeBridging(L1DAI.address, 1, user.address, chainIds[1], encodedTokenMetadata),
+ ).to.be.revertedWithCustomError(l1TokenBridge, "CallerIsNotMessageService");
+ });
+
+ it("Should revert if completeBridging message does not come from the remote Token Bridge", async function () {
+ const {
+ user,
+ messageService,
+ l1TokenBridge,
+ l2TokenBridge,
+ tokens: { L1DAI },
+ encodedTokenMetadata,
+ chainIds,
+ } = await loadFixture(deployContractsFixture);
+
+ await expect(
+ messageService.connect(user).sendMessage(
+ l2TokenBridge.address,
+ 0, // fee
+ l1TokenBridge.interface.encodeFunctionData(
+ // calldata
+ "completeBridging ",
+ [L1DAI.address, 1, user.address, chainIds[1], encodedTokenMetadata],
+ ),
+ ),
+ ).to.be.revertedWithCustomError(l1TokenBridge, "SenderNotAuthorized");
+ });
+
+ describe("setCustomContract", function () {
+ it("Should bridge EIP712-compliant-token with permit", async function () {
+ const {
+ user,
+ l1TokenBridge,
+ l2TokenBridge,
+ tokens: { L1DAI },
+ chainIds,
+ } = await loadFixture(deployContractsFixture);
+
+ const l1Token = L1DAI;
+ const bridgeAmount = 70;
+
+ // Bridge token L1 to L2
+ await l1TokenBridge.connect(user).bridgeToken(L1DAI.address, bridgeAmount, user.address);
+ const l2TokenAddress = await l2TokenBridge.nativeToBridgedToken(chainIds[0], l1Token.address);
+ const BridgedToken = await ethers.getContractFactory("BridgedToken");
+ const l2Token = BridgedToken.attach(l2TokenAddress);
+
+ // Check that no allowance exist for l2Token (User => l2TokenBridge)
+ expect(await l2Token.allowance(user.address, l2TokenBridge.address)).to.be.equal(0);
+
+ // Create EIP712-signature
+ const deadline = ethers.constants.MaxUint256;
+ const { chainId } = await ethers.provider.getNetwork();
+ const nonce = await l2Token.nonces(user.address);
+ expect(nonce).to.be.equal(0);
+
+ // Try to bridge back without permit data
+ await expect(
+ l2TokenBridge.connect(user).bridgeToken(l2Token.address, bridgeAmount, user.address),
+ ).to.be.revertedWith("ERC20: insufficient allowance");
+
+ // Capture balances before bridging back
+ const l1TokenUserBalanceBefore = await l1Token.balanceOf(user.address);
+ const l2TokenUserBalanceBefore = await l2Token.balanceOf(user.address);
+
+ // Prepare data for permit calldata
+ const permitData = await getPermitData(
+ user,
+ l2Token,
+ nonce,
+ chainId,
+ l2TokenBridge.address,
+ bridgeAmount,
+ deadline,
+ );
+
+ // Bridge back
+ await l2TokenBridge
+ .connect(user)
+ .bridgeTokenWithPermit(l2Token.address, bridgeAmount, user.address, permitData);
+
+ // Capture balances after bridging back
+ const l1TokenUserBalanceAfter = await l1Token.balanceOf(user.address);
+ const l2TokenUserBalanceAfter = await l2Token.balanceOf(user.address);
+
+ const diffL1UserBalance = l1TokenUserBalanceAfter.sub(l1TokenUserBalanceBefore);
+ const diffL2UserBalance = l2TokenUserBalanceBefore.sub(l2TokenUserBalanceAfter);
+
+ expect(diffL1UserBalance).to.be.equal(bridgeAmount);
+ expect(diffL2UserBalance).to.be.equal(bridgeAmount);
+ });
+ });
+
+ describe("setCustomContract", function () {
+ it("Should revert if setCustomContract is not called by the owner", async function () {
+ const { user, l1TokenBridge } = await loadFixture(deployContractsFixture);
+ await expect(l1TokenBridge.connect(user).setCustomContract(CUSTOM_ADDRESS, CUSTOM_ADDRESS)).to.be.revertedWith(
+ "Ownable: caller is not the owner",
+ );
+ });
+
+ it("Should revert if a native token has already been bridged", async function () {
+ const {
+ user,
+ owner,
+ l1TokenBridge,
+ l2TokenBridge,
+ tokens: { L1DAI },
+ chainIds,
+ } = await loadFixture(deployContractsFixture);
+ // First bridge token (user has L1DAI balance set in the fixture)
+ await l1TokenBridge.connect(user).bridgeToken(L1DAI.address, 1, user.address);
+ const l2TokenAddress = await l2TokenBridge.nativeToBridgedToken(chainIds[0], L1DAI.address);
+ await expect(
+ l1TokenBridge.connect(owner).setCustomContract(L1DAI.address, CUSTOM_ADDRESS),
+ ).to.be.revertedWithCustomError(l1TokenBridge, "AlreadyBridgedToken");
+ await expect(
+ l2TokenBridge.connect(owner).setCustomContract(l2TokenAddress, CUSTOM_ADDRESS),
+ ).to.be.revertedWithCustomError(l1TokenBridge, "AlreadyBridgedToken");
+ });
+ });
+
+ describe("Pause / unpause", function () {
+ it("Should pause the contract when pause() is called", async function () {
+ const { owner, l1TokenBridge } = await loadFixture(deployContractsFixture);
+
+ await l1TokenBridge.connect(owner).pause();
+ expect(await l1TokenBridge.paused()).to.equal(true);
+ });
+
+ it("Should unpause the contract when unpause() is called", async function () {
+ const { owner, l1TokenBridge } = await loadFixture(deployContractsFixture);
+
+ await l1TokenBridge.connect(owner).pause();
+
+ await l1TokenBridge.connect(owner).unpause();
+
+ expect(await l1TokenBridge.paused()).to.equal(false);
+ });
+ it("Should revert bridgeToken if paused", async function () {
+ const {
+ owner,
+ l1TokenBridge,
+ tokens: { L1DAI },
+ } = await loadFixture(deployContractsFixture);
+
+ await l1TokenBridge.connect(owner).pause();
+ await expect(l1TokenBridge.bridgeToken(L1DAI.address, 10, owner.address, [])).to.be.revertedWith(
+ "Pausable: paused",
+ );
+ });
+ it("Should allow bridgeToken if unpaused", async function () {
+ const {
+ owner,
+ user,
+ l1TokenBridge,
+ tokens: { L1DAI },
+ } = await loadFixture(deployContractsFixture);
+
+ await l1TokenBridge.connect(owner).pause();
+ await l1TokenBridge.connect(owner).unpause();
+ await l1TokenBridge.connect(user).bridgeToken(L1DAI.address, 10, user.address, []);
+ });
+ });
+
+ describe("Owner", function () {
+ it("Should revert if setReservedToken is called by a non-owner", async function () {
+ const { user, l1TokenBridge } = await loadFixture(deployContractsFixture);
+ await expect(l1TokenBridge.connect(user).setReserved(user.address)).to.be.revertedWith(
+ "Ownable: caller is not the owner",
+ );
+ });
+ it("Should revert if pause() is called by a non-owner", async function () {
+ const { user, l1TokenBridge } = await loadFixture(deployContractsFixture);
+
+ await expect(l1TokenBridge.connect(user).pause()).to.be.revertedWith("Ownable: caller is not the owner");
+ });
+ it("Should revert if unpause() is called by a non-owner", async function () {
+ const { user, l1TokenBridge } = await loadFixture(deployContractsFixture);
+
+ await expect(l1TokenBridge.connect(user).unpause()).to.be.revertedWith("Ownable: caller is not the owner");
+ });
+ it("Should revert if transferOwnership is called by a non-owner", async function () {
+ const { user, l1TokenBridge } = await loadFixture(deployContractsFixture);
+ await expect(l1TokenBridge.connect(user).transferOwnership(user.address)).to.be.revertedWith(
+ "Ownable: caller is not the owner",
+ );
+ });
+ it("Should revert if removeReserved is called by a non-owner", async function () {
+ const {
+ user,
+ l1TokenBridge,
+ tokens: { L1DAI },
+ } = await loadFixture(deployContractsFixture);
+ await expect(l1TokenBridge.connect(user).removeReserved(L1DAI.address)).to.be.revertedWith(
+ "Ownable: caller is not the owner",
+ );
+ });
+ });
+ });
+
+ describe("Reserved tokens", function () {
+ it("Should be possible for the admin to reserve a token", async function () {
+ const {
+ owner,
+ l1TokenBridge,
+ tokens: { L1DAI },
+ chainIds,
+ } = await loadFixture(deployContractsFixture);
+ await expect(l1TokenBridge.connect(owner).setReserved(L1DAI.address)).not.to.be.revertedWith(
+ "TokenBridge: token already bridged",
+ );
+ expect(await l1TokenBridge.nativeToBridgedToken(chainIds[0], L1DAI.address)).to.be.equal(RESERVED_STATUS);
+ });
+
+ it("Should be possible for the admin to remove token from the reserved list", async function () {
+ // @TODO this test can probably be rewritten, avoiding to set the token as reserved in the first place
+ const {
+ owner,
+ l1TokenBridge,
+ tokens: { L1DAI },
+ chainIds,
+ } = await loadFixture(deployContractsFixture);
+ await l1TokenBridge.connect(owner).setReserved(L1DAI.address);
+ expect(await l1TokenBridge.nativeToBridgedToken(chainIds[0], L1DAI.address)).to.be.equal(RESERVED_STATUS);
+ await l1TokenBridge.connect(owner).removeReserved(L1DAI.address);
+ expect(await l1TokenBridge.nativeToBridgedToken(chainIds[0], L1DAI.address)).to.be.equal(
+ ethers.constants.AddressZero,
+ );
+ });
+
+ it("Should not be possible to bridge reserved tokens", async function () {
+ const {
+ owner,
+ user,
+ l1TokenBridge,
+ tokens: { L1DAI },
+ } = await loadFixture(deployContractsFixture);
+ await l1TokenBridge.connect(owner).setReserved(L1DAI.address);
+ await expect(
+ l1TokenBridge.connect(user).bridgeToken(L1DAI.address, 1, user.address),
+ ).to.be.revertedWithCustomError(l1TokenBridge, "ReservedToken");
+ });
+
+ it("Should only be possible to reserve a token if it has not been bridged before", async function () {
+ const {
+ owner,
+ user,
+ l1TokenBridge,
+ tokens: { L1DAI },
+ } = await loadFixture(deployContractsFixture);
+ l1TokenBridge.connect(user).bridgeToken(L1DAI.address, 1, user.address);
+ await expect(l1TokenBridge.connect(owner).setReserved(L1DAI.address)).to.be.revertedWithCustomError(
+ l1TokenBridge,
+ "AlreadyBridgedToken",
+ );
+ });
+
+ it("Should set reserved tokens in the initializer", async function () {
+ const { chainIds } = await loadFixture(deployContractsFixture);
+ const TokenBridgeFactory = await ethers.getContractFactory("TokenBridge");
+ const l1TokenBridge = await upgrades.deployProxy(TokenBridgeFactory, [
+ PLACEHOLDER_ADDRESS, // owner
+ PLACEHOLDER_ADDRESS, // messageService
+ PLACEHOLDER_ADDRESS, // tokenBeacon
+ chainIds[0],
+ chainIds[1],
+ [CUSTOM_ADDRESS], // reservedTokens
+ ]);
+ await l1TokenBridge.deployed();
+ expect(await l1TokenBridge.nativeToBridgedToken(chainIds[0], CUSTOM_ADDRESS)).to.be.equal(RESERVED_STATUS);
+ });
+
+ it("Should only be possible to call removeReserved if the token is in the reserved list", async function () {
+ const {
+ owner,
+ l1TokenBridge,
+ tokens: { L1DAI },
+ } = await loadFixture(deployContractsFixture);
+ await expect(l1TokenBridge.connect(owner).removeReserved(L1DAI.address)).to.be.revertedWithCustomError(
+ l1TokenBridge,
+ "NotReserved",
+ );
+ });
+
+ it("Should revert if token is the 0 address", async function () {
+ const { owner, l1TokenBridge } = await loadFixture(deployContractsFixture);
+ await expect(
+ l1TokenBridge.connect(owner).setReserved(ethers.constants.AddressZero),
+ ).to.be.revertedWithCustomError(l1TokenBridge, "ZeroAddressNotAllowed");
+ });
+ });
+
+ describe("bridgeTokenWithPermit", function () {
+ it("Should revert if contract is paused", async function () {
+ const {
+ user,
+ tokens: { L1DAI },
+ l1TokenBridge,
+ } = await loadFixture(deployContractsFixture);
+ await l1TokenBridge.pause();
+ await expect(
+ l1TokenBridge.connect(user).bridgeTokenWithPermit(L1DAI.address, 1, user.address, EMPTY_PERMIT_DATA),
+ ).to.be.revertedWith("Pausable: paused");
+ });
+
+ it("Should revert if token is the 0 address", async function () {
+ const { user, l1TokenBridge } = await loadFixture(deployContractsFixture);
+ await expect(
+ l1TokenBridge
+ .connect(user)
+ .bridgeTokenWithPermit(ethers.constants.AddressZero, 1, user.address, EMPTY_PERMIT_DATA),
+ ).to.be.revertedWithCustomError(l1TokenBridge, "ZeroAddressNotAllowed");
+ });
+
+ it("Should revert if token is the amount is 0", async function () {
+ const {
+ user,
+ l1TokenBridge,
+ tokens: { L1DAI },
+ } = await loadFixture(deployContractsFixture);
+ await expect(
+ l1TokenBridge.connect(user).bridgeTokenWithPermit(L1DAI.address, 0, user.address, EMPTY_PERMIT_DATA),
+ ).to.be.revertedWithCustomError(l1TokenBridge, "ZeroAmountNotAllowed");
+ });
+
+ it("Should not revert if permitData is empty", async function () {
+ const {
+ user,
+ l1TokenBridge,
+ tokens: { L1DAI },
+ } = await loadFixture(deployContractsFixture);
+ await expect(
+ l1TokenBridge.connect(user).bridgeTokenWithPermit(L1DAI.address, 10, user.address, EMPTY_PERMIT_DATA),
+ ).to.be.not.reverted;
+ });
+
+ it("Should revert if permitData is invalid", async function () {
+ const {
+ owner,
+ user,
+ l1TokenBridge,
+ l2TokenBridge,
+ tokens: { L1DAI },
+ chainIds,
+ } = await loadFixture(deployContractsFixture);
+ // Test when the permitData has an invalid format
+ await expect(
+ l1TokenBridge.connect(user).bridgeTokenWithPermit(L1DAI.address, 10, user.address, "0x111111111111"),
+ ).to.be.revertedWithCustomError(l1TokenBridge, "InvalidPermitData");
+
+ // Test when the spender passed is invalid
+ // Prepare data for permit calldata
+ const bridgeAmount = 70;
+
+ // Bridge token L1 to L2
+ await l1TokenBridge.connect(user).bridgeToken(L1DAI.address, bridgeAmount, user.address);
+ const l2TokenAddress = await l2TokenBridge.nativeToBridgedToken(chainIds[0], L1DAI.address);
+ const BridgedToken = await ethers.getContractFactory("BridgedToken");
+ const l2Token = BridgedToken.attach(l2TokenAddress);
+
+ // Create EIP712-signature
+ const deadline = ethers.constants.MaxUint256;
+ const { chainId } = await ethers.provider.getNetwork();
+ const nonce = await l2Token.nonces(user.address);
+
+ let permitData = await getPermitData(user, l2Token, nonce, chainId, user.address, bridgeAmount, deadline);
+ await expect(
+ l2TokenBridge.connect(user).bridgeTokenWithPermit(L1DAI.address, bridgeAmount, user.address, permitData),
+ ).to.be.revertedWithCustomError(l1TokenBridge, "PermitNotAllowingBridge");
+
+ // Test when the sender is not the owner of the tokens
+ permitData = await getPermitData(user, l2Token, nonce, chainId, l2TokenBridge.address, bridgeAmount, deadline);
+ await expect(
+ l1TokenBridge.connect(owner).bridgeTokenWithPermit(L1DAI.address, bridgeAmount, user.address, permitData),
+ ).to.be.revertedWithCustomError(l1TokenBridge, "PermitNotFromSender");
+ });
+ });
+
+ describe("bridgeToken", function () {
+ it("Should not emit event NewToken if the token has already been bridged once", async function () {
+ const {
+ user,
+ tokens: { L1DAI },
+ l1TokenBridge,
+ } = await loadFixture(deployContractsFixture);
+ await expect(l1TokenBridge.connect(user).bridgeToken(L1DAI.address, 1, user.address)).to.emit(
+ l1TokenBridge,
+ "NewToken",
+ );
+ await expect(l1TokenBridge.connect(user).bridgeToken(L1DAI.address, 1, user.address)).to.not.emit(
+ l1TokenBridge,
+ "NewToken",
+ );
+ });
+
+ it("Should revert if recipient is set at 0 address", async function () {
+ const {
+ user,
+ tokens: { L1DAI },
+ l1TokenBridge,
+ } = await loadFixture(deployContractsFixture);
+ await expect(
+ l1TokenBridge.connect(user).bridgeToken(L1DAI.address, 1, ethers.constants.AddressZero),
+ ).to.revertedWithCustomError(l1TokenBridge, "ZeroAddressNotAllowed");
+ });
+
+ it("Should not be able to call bridgeToken by reentrancy", async function () {
+ const { owner, l1TokenBridge } = await loadFixture(deployContractsFixture);
+
+ const ReentrancyContract = await ethers.getContractFactory("ReentrancyContract");
+ const reentrancyContract = await ReentrancyContract.deploy(l1TokenBridge.address);
+
+ const MaliciousERC777 = await ethers.getContractFactory("MaliciousERC777");
+ const maliciousERC777 = await MaliciousERC777.deploy(reentrancyContract.address);
+ await maliciousERC777.mint(reentrancyContract.address, 100);
+ await maliciousERC777.mint(owner.address, 100);
+
+ await reentrancyContract.setToken(maliciousERC777.address);
+
+ await expect(l1TokenBridge.bridgeToken(maliciousERC777.address, 1, owner.address)).to.be.revertedWith(
+ "ReentrancyGuard: reentrant call",
+ );
+ });
+ });
+
+ describe("setRemoteTokenBridge", function () {
+ it("Should revert if remoteTokenBridge has not been initialized", async function () {
+ const { owner, l1TokenBridge } = await loadFixture(deployContractsFixture);
+ await expect(l1TokenBridge.connect(owner).setRemoteTokenBridge(l1TokenBridge.address)).to.revertedWithCustomError(
+ l1TokenBridge,
+ "RemoteTokenBridgeAlreadySet",
+ );
+ });
+
+ it("Should revert if called by non-owner", async function () {
+ const { user, l1TokenBridge } = await loadFixture(deployContractsFixture);
+ await expect(l1TokenBridge.connect(user).setRemoteTokenBridge(l1TokenBridge.address)).to.revertedWith(
+ "Ownable: caller is not the owner",
+ );
+ });
+ });
+
+ describe("setDeployed", function () {
+ it("Should revert if not called by the messageService", async function () {
+ const { user, l1TokenBridge } = await loadFixture(deployContractsFixture);
+ await expect(l1TokenBridge.connect(user).setDeployed([])).to.be.revertedWithCustomError(
+ l1TokenBridge,
+ "CallerIsNotMessageService",
+ );
+ });
+
+ it("Should revert if message does not come from the remote Token Bridge", async function () {
+ const { user, messageService, l1TokenBridge, l2TokenBridge } = await loadFixture(deployContractsFixture);
+
+ await expect(
+ messageService.connect(user).sendMessage(
+ l2TokenBridge.address,
+ 0, // fee
+ l1TokenBridge.interface.encodeFunctionData(
+ // calldata
+ "setDeployed",
+ [[]],
+ ),
+ ),
+ ).to.be.revertedWithCustomError(l1TokenBridge, "SenderNotAuthorized");
+ });
+ });
+});
diff --git a/contracts/test/tokenBridge/utils/permitHelper.ts b/contracts/test/tokenBridge/utils/permitHelper.ts
new file mode 100644
index 000000000..7e741b350
--- /dev/null
+++ b/contracts/test/tokenBridge/utils/permitHelper.ts
@@ -0,0 +1,46 @@
+import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
+import { BigNumber, Contract } from "ethers";
+import { ethers } from "hardhat";
+
+const Permit = [
+ { name: "owner", type: "address" },
+ { name: "spender", type: "address" },
+ { name: "value", type: "uint256" },
+ { name: "nonce", type: "uint256" },
+ { name: "deadline", type: "uint256" },
+];
+
+function buildData(
+ owner: string,
+ name: string,
+ version: string,
+ chainId: number,
+ verifyingContract: string,
+ spender: string,
+ value: number,
+ nonce: BigNumber,
+ deadline: BigNumber,
+) {
+ return {
+ domain: { name, version, chainId, verifyingContract },
+ types: { Permit },
+ value: { owner, spender, value, nonce, deadline },
+ };
+}
+
+export async function getPermitData(
+ wallet: SignerWithAddress,
+ token: Contract,
+ nonce: BigNumber,
+ chainId: number,
+ spender: string,
+ value: number,
+ deadline: BigNumber,
+) {
+ const name = await token.name();
+ const data = buildData(wallet.address, name, "1", chainId, token.address, spender, value, nonce, deadline);
+ const signature = await wallet._signTypedData(data.domain, data.types, data.value);
+ const { v, r, s } = ethers.utils.splitSignature(signature);
+ const permitCall = token.interface.encodeFunctionData("permit", [wallet.address, spender, value, deadline, v, r, s]);
+ return permitCall;
+}
diff --git a/contracts/test/utils/constants.ts b/contracts/test/utils/constants.ts
new file mode 100644
index 000000000..c596f1bfd
--- /dev/null
+++ b/contracts/test/utils/constants.ts
@@ -0,0 +1,64 @@
+import { ethers } from "hardhat";
+
+// TODO FIX IMPORT
+const generateKeccak256Hash = (str: string) => ethers.utils.keccak256(ethers.utils.toUtf8Bytes(str));
+
+// TimeLock roles
+export const TIMELOCK_ADMIN_ROLE = generateKeccak256Hash("TIMELOCK_ADMIN_ROLE");
+export const PROPOSER_ROLE = generateKeccak256Hash("PROPOSER_ROLE");
+export const EXECUTOR_ROLE = generateKeccak256Hash("EXECUTOR_ROLE");
+export const CANCELLER_ROLE = generateKeccak256Hash("CANCELLER_ROLE");
+
+// Roles hashes
+export const DEFAULT_ADMIN_ROLE = ethers.constants.HashZero;
+export const RATE_LIMIT_SETTER_ROLE = generateKeccak256Hash("RATE_LIMIT_SETTER_ROLE");
+export const L1_L2_MESSAGE_SETTER_ROLE = generateKeccak256Hash("L1_L2_MESSAGE_SETTER_ROLE");
+export const PAUSE_MANAGER_ROLE = generateKeccak256Hash("PAUSE_MANAGER_ROLE");
+export const MINIMUM_FEE_SETTER_ROLE = generateKeccak256Hash("MINIMUM_FEE_SETTER_ROLE");
+export const OPERATOR_ROLE = generateKeccak256Hash("OPERATOR_ROLE");
+export const BAD_STARTING_HASH = generateKeccak256Hash("BAD_STARTING_HASH");
+
+export const INITIAL_MIGRATION_BLOCK = 0;
+export const L1_L2_PAUSE_TYPE = generateKeccak256Hash("L1_L2_PAUSE_TYPE");
+export const L2_L1_PAUSE_TYPE = generateKeccak256Hash("L2_L1_PAUSE_TYPE");
+export const PROVING_SYSTEM_PAUSE_TYPE = generateKeccak256Hash("PROVING_SYSTEM_PAUSE_TYPE");
+export const GENERAL_PAUSE_TYPE = generateKeccak256Hash("GENERAL_PAUSE_TYPE");
+
+export const INBOX_STATUS_UNKNOWN = 0;
+export const INBOX_STATUS_RECEIVED = 1;
+export const INBOX_STATUS_CLAIMED = 2;
+
+export const OUTBOX_STATUS_UNKNOWN = 0;
+export const OUTBOX_STATUS_SENT = 1;
+export const OUTBOX_STATUS_RECEIVED = 2;
+
+export const ONE_DAY_IN_SECONDS = 86_400;
+export const INITIAL_WITHDRAW_LIMIT = ethers.utils.parseEther("5");
+
+export const MESSAGE_VALUE_1ETH = ethers.utils.parseEther("1");
+export const ZERO_VALUE = 0;
+export const MESSAGE_FEE = ethers.utils.parseEther("0.05");
+export const LOW_NO_REFUND_MESSAGE_FEE = ethers.utils.parseEther("0.00001");
+export const MINIMUM_FEE = ethers.utils.parseEther("0.1");
+export const DEFAULT_MESSAGE_NONCE = ethers.utils.parseEther("123456789");
+export const SAMPLE_FUNCTION_CALLDATA = generateKeccak256Hash("callThisFunction()").substring(0, 10); //0x + 4bytes
+export const EMPTY_CALLDATA = "0x";
+export const BLOCK_COINBASE = "0xc014ba5ec014ba5ec014ba5ec014ba5ec014ba5e";
+
+// TODO CLEANUP TO MAKE THIS DYNAMIC AND NOT CONSTANT
+export const Add_L1L2_Message_Hashes_Calldata_With_Empty_Array =
+ "0xf4b476e10000000000000000000000000000000000000000000000000000000000000000";
+export const Add_L1L2_Message_Hashes_Calldata_With_One_Hash =
+ "0xf4b476e100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001f57d2ce8b7fc0df7ae7cbddaa706242a118bd8b649abccfecfb3f8e419729ca9";
+export const Single_Item_L1L2_HashArray = ["0xf57d2ce8b7fc0df7ae7cbddaa706242a118bd8b649abccfecfb3f8e419729ca9"];
+
+// TODO CLEANUP TO MAKE THIS DYNAMIC AND NOT CONSTANT
+export const Add_L1L2_Message_Hashes_Calldata_With_Five_Hashes =
+ "0xf4b476e100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005f887bbc07b0e849fb625aafadf4cb6b65b98e492fbb689705312bf1db98ead7fdd87bbc07b0e849fb625aafadf4cb6b65b98e492fbb689705312bf1db98ead7faa87bbc07b0e849fb625aafadf4cb6b65b98e492fbb689705312bf1db98ead7fcc87bbc07b0e849fb625aafadf4cb6b65b98e492fbb689705312bf1db98ead7f1187bbc07b0e849fb625aafadf4cb6b65b98e492fbb689705312bf1db98ead7f";
+export const L1L2_FiveHashes = [
+ "0xf887bbc07b0e849fb625aafadf4cb6b65b98e492fbb689705312bf1db98ead7f",
+ "0xdd87bbc07b0e849fb625aafadf4cb6b65b98e492fbb689705312bf1db98ead7f",
+ "0xaa87bbc07b0e849fb625aafadf4cb6b65b98e492fbb689705312bf1db98ead7f",
+ "0xcc87bbc07b0e849fb625aafadf4cb6b65b98e492fbb689705312bf1db98ead7f",
+ "0x1187bbc07b0e849fb625aafadf4cb6b65b98e492fbb689705312bf1db98ead7f",
+];
diff --git a/contracts/test/utils/deployment.ts b/contracts/test/utils/deployment.ts
new file mode 100644
index 000000000..53ad3a8ec
--- /dev/null
+++ b/contracts/test/utils/deployment.ts
@@ -0,0 +1,24 @@
+import { DeployProxyOptions } from "@openzeppelin/hardhat-upgrades/src/utils";
+import { ethers, upgrades } from "hardhat";
+import { FactoryOptions } from "hardhat/types";
+
+async function deployFromFactory(contractName: string, ...args: unknown[]) {
+ const factory = await ethers.getContractFactory(contractName);
+ const contract = await factory.deploy(...args);
+ await contract.deployed();
+ return contract;
+}
+
+async function deployUpgradableFromFactory(
+ contractName: string,
+ args?: unknown[],
+ opts?: DeployProxyOptions,
+ factoryOpts?: FactoryOptions,
+) {
+ const factory = await ethers.getContractFactory(contractName, factoryOpts);
+ const contract = await upgrades.deployProxy(factory, args, opts);
+ await contract.deployed();
+ return contract;
+}
+
+export { deployFromFactory, deployUpgradableFromFactory };
diff --git a/contracts/test/utils/helpers.ts b/contracts/test/utils/helpers.ts
new file mode 100644
index 000000000..fab55f8d5
--- /dev/null
+++ b/contracts/test/utils/helpers.ts
@@ -0,0 +1,120 @@
+import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
+import { BigNumber } from "ethers";
+import fs from "fs";
+import { ethers } from "hardhat";
+import path from "path";
+import { DEFAULT_MESSAGE_NONCE, MESSAGE_FEE, MESSAGE_VALUE_1ETH } from "./constants";
+import { DebugData, FormattedBlockData, RawBlockData } from "./types";
+
+export const generateKeccak256Hash = (str: string) => ethers.utils.keccak256(ethers.utils.toUtf8Bytes(str));
+
+export const generateNKeccak256Hashes = (str: string, numberOfHashToGenerate: number): string[] => {
+ let arr: string[] = [];
+ for (let i = 1; i < numberOfHashToGenerate + 1; i++) {
+ arr = [...arr, ethers.utils.keccak256(ethers.utils.toUtf8Bytes(`${str}${i}`))];
+ }
+ return arr;
+};
+
+export async function encodeSendMessageLog(
+ sender: SignerWithAddress,
+ receiver: SignerWithAddress,
+ messageHash: string,
+ calldata: string,
+) {
+ const abiCoder = new ethers.utils.AbiCoder();
+ const topic = ethers.utils.id("MessageSent(address,address,uint256,uint256,uint256,bytes,bytes32)");
+ const data = abiCoder.encode(
+ ["address", "address", "uint256", "uint256", "uint256", "bytes", "bytes32"],
+ [sender.address, receiver.address, MESSAGE_FEE, MESSAGE_VALUE_1ETH, DEFAULT_MESSAGE_NONCE, calldata, messageHash],
+ );
+
+ return {
+ topic,
+ data,
+ };
+}
+
+export async function encodeSendMessage(
+ sender: string,
+ receiver: string,
+ fee: BigNumber,
+ amount: BigNumber,
+ salt: BigNumber,
+ calldata: string,
+) {
+ const abiCoder = ethers.utils.defaultAbiCoder;
+ const data = abiCoder.encode(
+ ["address", "address", "uint256", "uint256", "uint256", "bytes"],
+ [sender, receiver, fee, amount, salt, calldata],
+ );
+
+ return data;
+}
+
+export function getProverTestData(
+ filename: string,
+ folder: string,
+): {
+ blocks: FormattedBlockData[];
+ proverMode: string;
+ parentStateRootHash: string;
+ version: string;
+ firstBlockNumber: number;
+ proof: string;
+ debugData: DebugData;
+} {
+ const testFilePath = path.resolve(__dirname, "..", "testData", filename, folder);
+ const testData = JSON.parse(fs.readFileSync(testFilePath, "utf8"));
+
+ return {
+ blocks: testData.blocksData.map((block: RawBlockData) => ({
+ blockRootHash: block.rootHash,
+ transactions: block.rlpEncodedTransactions,
+ l2BlockTimestamp: block.timestamp,
+ l2ToL1MsgHashes: block.l2ToL1MsgHashes,
+ fromAddresses: block.fromAddresses,
+ batchReceptionIndices: block.batchReceptionIndices,
+ })),
+ proverMode: testData.proverMode,
+ parentStateRootHash: testData.parentStateRootHash,
+ version: testData.version,
+ firstBlockNumber: testData.firstBlockNumber,
+ proof: testData.proof,
+ debugData: testData.DebugData,
+ };
+}
+
+export function getRLPEncodeTransactions(filename: string): {
+ shortEip1559Transaction: string;
+ eip1559Transaction: string;
+ eip1559TransactionHashes: string[];
+ legacyTransaction: string;
+ legacyTransactionHashes: string[];
+ eip2930Transaction: string;
+ eip2930TransactionHashes: string[];
+} {
+ const testFilePath = path.resolve(__dirname, "..", "testData", filename);
+ const testData = JSON.parse(fs.readFileSync(testFilePath, "utf8"));
+
+ return {
+ shortEip1559Transaction: testData.shortEip1559Transaction,
+ eip1559Transaction: testData.eip1559Transaction,
+ eip1559TransactionHashes: testData.eip1559TransactionHashes,
+ legacyTransaction: testData.legacyTransaction,
+ legacyTransactionHashes: testData.legacyTransactionHashes,
+ eip2930Transaction: testData.eip2930Transaction,
+ eip2930TransactionHashes: testData.eip2930TransactionHashes,
+ };
+}
+
+export function getTransactionsToBeDecoded(blocks: FormattedBlockData[]): string[] {
+ const txsToBeDecoded = [];
+ for (let i = 0; i < blocks.length; i++) {
+ for (let j = 0; j < blocks[i].batchReceptionIndices.length; j++) {
+ const txIndex = blocks[i].batchReceptionIndices[j];
+ txsToBeDecoded.push(blocks[i].transactions[txIndex]);
+ }
+ }
+ return txsToBeDecoded;
+}
diff --git a/contracts/test/utils/types.ts b/contracts/test/utils/types.ts
new file mode 100644
index 000000000..870289f3b
--- /dev/null
+++ b/contracts/test/utils/types.ts
@@ -0,0 +1,34 @@
+export type RawBlockData = {
+ rootHash: string;
+ timestamp: number;
+ rlpEncodedTransactions: string[];
+ l2ToL1MsgHashes: string[];
+ batchReceptionIndices: number[];
+ fromAddresses: string;
+};
+
+export type FormattedBlockData = Omit<
+ RawBlockData,
+ "rlpEncodedTransactions" | "timestamp" | "l2ToL1MsgHashes" | "fromAddresses" | "rootHash"
+> & {
+ l2BlockTimestamp: number;
+ transactions: string[];
+ l2ToL1MsgHashes: string[];
+ fromAddresses: string;
+ blockRootHash: string;
+};
+
+export type DebugData = {
+ blocks: {
+ txHashes: string[];
+ hashOfTxHashes: string;
+ logHashes: string[];
+ hashOfLogHashes: string;
+ hashOfPositions: string;
+ HashForBlock: string;
+ }[];
+ hashForAllBlocks: string;
+ hashOfRootHashes: string;
+ timestampHashes: string;
+ finalHash: string;
+};
diff --git a/contracts/test/verifiers/TestPlonkVerifier.ts b/contracts/test/verifiers/TestPlonkVerifier.ts
new file mode 100644
index 000000000..c72831e89
--- /dev/null
+++ b/contracts/test/verifiers/TestPlonkVerifier.ts
@@ -0,0 +1,33 @@
+import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
+import { expect } from "chai";
+import { BigNumber } from "ethers";
+import { TestPlonkVerifier } from "../../typechain-types";
+import { deployFromFactory } from "../utils/deployment";
+import { getProverTestData } from "../utils/helpers";
+
+describe("test plonk", () => {
+ let zkEvm: TestPlonkVerifier;
+
+ async function deployZkEvmFixture() {
+ return deployFromFactory("TestPlonkVerifier") as Promise;
+ }
+
+ beforeEach(async () => {
+ zkEvm = await loadFixture(deployZkEvmFixture);
+ });
+
+ describe("test_verifier_go", () => {
+ it("Should succeed", async () => {
+ const {
+ proof,
+ debugData: { finalHash },
+ } = getProverTestData("Light", "output-file.json");
+
+ expect(
+ await zkEvm.test_verifier_go(proof, [BigNumber.from(finalHash)], {
+ gasLimit: 500_000,
+ }),
+ ).to.not.be.reverted;
+ });
+ });
+});
diff --git a/contracts/test/verifiers/TestPlonkVerifierFull.ts b/contracts/test/verifiers/TestPlonkVerifierFull.ts
new file mode 100644
index 000000000..31d96edcf
--- /dev/null
+++ b/contracts/test/verifiers/TestPlonkVerifierFull.ts
@@ -0,0 +1,33 @@
+import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
+import { expect } from "chai";
+import { BigNumber } from "ethers";
+import { TestPlonkVerifierFull } from "../../typechain-types";
+import { deployFromFactory } from "../utils/deployment";
+import { getProverTestData } from "./../utils/helpers";
+
+describe("test plonk", () => {
+ let plonkVerifier: TestPlonkVerifierFull;
+
+ const PROOF_MODE = "Full";
+ const { proof } = getProverTestData(PROOF_MODE, "output-file.json");
+
+ async function deployPlonkVerifierFixture() {
+ return deployFromFactory("TestPlonkVerifierFull") as Promise;
+ }
+
+ beforeEach(async () => {
+ plonkVerifier = await loadFixture(deployPlonkVerifierFixture);
+ });
+
+ describe("testVerifier_go", () => {
+ it("Should verify proof successfully", async () => {
+ expect(
+ await plonkVerifier.testVerifier(
+ proof,
+ [
+ BigNumber.from("21826973039313591084461008381720124636263968477099612249120776239336034572329"),
+ ]),
+ ).to.not.be.reverted;
+ });
+ });
+});
diff --git a/contracts/test/verifiers/TestPlonkVerifierFullLarge.ts b/contracts/test/verifiers/TestPlonkVerifierFullLarge.ts
new file mode 100644
index 000000000..aec9d8ac7
--- /dev/null
+++ b/contracts/test/verifiers/TestPlonkVerifierFullLarge.ts
@@ -0,0 +1,32 @@
+import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
+import { expect } from "chai";
+import { BigNumber } from "ethers";
+import { TestPlonkVerifierFullLarge } from "../../typechain-types";
+import { deployFromFactory } from "../utils/deployment";
+import { getProverTestData } from "./../utils/helpers";
+
+describe("test plonk", () => {
+ let plonkVerifier: TestPlonkVerifierFullLarge;
+
+ const PROOF_MODE = "FullLarge";
+ const { proof } = getProverTestData(PROOF_MODE, "output-file.json");
+
+ async function deployPlonkVerifierFixture() {
+ return deployFromFactory("TestPlonkVerifierFullLarge") as Promise;
+ }
+
+ beforeEach(async () => {
+ plonkVerifier = await loadFixture(deployPlonkVerifierFixture);
+ });
+
+ describe("testVerifier_go", () => {
+ it("Should verify proof successfully", async () => {
+ expect(await plonkVerifier.testVerifier(
+ proof,
+ [
+ BigNumber.from("21826973039313591084461008381720124636263968477099612249120776239336034572329"),
+ ]),
+ ).to.not.be.reverted;
+ });
+ });
+});
diff --git a/contracts/tsconfig.json b/contracts/tsconfig.json
new file mode 100644
index 000000000..9fee04834
--- /dev/null
+++ b/contracts/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "compilerOptions": {
+ "target": "es2021",
+ "module": "commonjs",
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "strict": true,
+ "skipLibCheck": true,
+ "resolveJsonModule": true
+ }
+}
diff --git a/contracts/utils/storeAddress.ts b/contracts/utils/storeAddress.ts
new file mode 100644
index 000000000..56f0120d6
--- /dev/null
+++ b/contracts/utils/storeAddress.ts
@@ -0,0 +1,11 @@
+/* eslint-disable @typescript-eslint/no-var-requires */
+const editJsonFile = require("edit-json-file");
+
+export const storeAddress = async (contractName: string, address: string, networkName: string) => {
+ const file = editJsonFile(`./deployments.json`);
+ file.set([networkName], networkName);
+ file.set([networkName] + "." + [contractName], address);
+ file.save();
+};
+
+export const delay = (ms: number) => new Promise((res) => setTimeout(res, ms));
diff --git a/coordinator/Dockerfile b/coordinator/Dockerfile
new file mode 100644
index 000000000..a95a3f2ed
--- /dev/null
+++ b/coordinator/Dockerfile
@@ -0,0 +1,33 @@
+FROM openjdk:17-slim-bullseye
+
+RUN apt-get update \
+ && apt-get install curl -y \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/*
+
+WORKDIR /opt/consensys/linea/coordinator
+
+# copy application
+COPY --from=jar ./app-all.jar /opt/consensys/linea/coordinator/libs/
+
+RUN mkdir -p /opt/consensys/linea/coordinator/logs
+RUN mkdir -p /opt/consensys/linea/coordinator/tmp/prover/request
+RUN mkdir -p /opt/consensys/linea/coordinator/tmp/prover/response
+
+# Build-time metadata as defined at http://label-schema.org
+ARG BUILD_DATE
+ARG VCS_REF
+ARG VERSION
+LABEL org.label-schema.build-date=$BUILD_DATE \
+ org.label-schema.name="coordinator" \
+ org.label-schema.description="Coordinator for Linea" \
+ org.label-schema.url="https://consensys.net/" \
+ org.label-schema.vcs-ref=$VCS_REF \
+ org.label-schema.vcs-url="https://github.com/ConsenSys/zkevm-monorepo" \
+ org.label-schema.vendor="ConsenSys" \
+ org.label-schema.version=$VERSION \
+ org.label-schema.schema-version="1.0"
+
+WORKDIR /opt/consensys/linea/coordinator/
+
+
diff --git a/coordinator/app/build.gradle b/coordinator/app/build.gradle
new file mode 100644
index 000000000..15f4966bf
--- /dev/null
+++ b/coordinator/app/build.gradle
@@ -0,0 +1,223 @@
+import org.gradle.api.tasks.testing.logging.TestExceptionFormat
+import org.gradle.api.tasks.testing.logging.TestLogEvent
+
+import java.time.Duration
+import java.util.concurrent.TimeUnit
+import java.util.regex.Matcher
+import java.util.regex.Pattern
+
+plugins {
+ id 'net.consensys.zkevm.kotlin-application-conventions'
+ id 'com.github.johnrengelman.shadow' version '7.1.2'
+ id "com.avast.gradle.docker-compose" version "0.14.2"
+}
+
+dependencies {
+ implementation project(':jvm-libs:json-rpc')
+ implementation project(':jvm-libs:http-rest')
+ implementation project(':jvm-libs:vertx-helper')
+ implementation project(':jvm-libs:future-extensions')
+ implementation project(':jvm-libs:web3j-extensions')
+ implementation project(':coordinator:utilities')
+ implementation project(':coordinator:core')
+ implementation project(':coordinator:clock-consensus')
+ implementation project(':coordinator:clients:persistence')
+ implementation project(':coordinator:clients:engine-api-client')
+ implementation project(':coordinator:clients:traces-generator-api-client')
+ implementation project(':coordinator:clients:type2-state-manager-client')
+ implementation project(':coordinator:clients:prover-client')
+ implementation project(':coordinator:clients:smart-contract-client')
+ implementation project(':coordinator:ethereum:batch-submitter')
+ implementation project(':coordinator:clients:web3signer-client')
+ implementation "tech.pegasys.teku.internal:unsigned:${versions.teku}"
+ implementation "tech.pegasys.teku.internal:logging:${versions.teku}"
+ implementation "tech.pegasys.teku.internal:infrastructure-events:${versions.teku}"
+
+ implementation "info.picocli:picocli:${versions.picoli}"
+ implementation "io.vertx:vertx-micrometer-metrics:${versions.vertx}"
+ implementation "io.vertx:vertx-web-client:${versions.vertx}"
+ implementation "io.micrometer:micrometer-registry-prometheus:${versions.micrometer}"
+ implementation "com.sksamuel.hoplite:hoplite-core:${versions.hoplite}"
+ implementation "com.sksamuel.hoplite:hoplite-toml:${versions.hoplite}"
+ implementation("org.web3j:core:${versions.web3j}") {
+ exclude group: 'org.slf4j', module: 'slf4j-nop'
+ }
+ api("io.netty:netty-transport-native-epoll:${versions.netty}:linux-x86_64") {
+ because "It enables native transport for Linux."
+ // Note that its version should match netty version used in Vertx
+ }
+ api("io.netty:netty-transport-native-kqueue:${versions.netty}:osx-x86_64") {
+ because "It enables native transport for Mac OSX."
+ // Note that its version should match netty version used in Vertx
+ }
+ // Transaction encoding
+ implementation "org.hyperledger.besu:besu-datatypes:${versions.besu}"
+ implementation "org.hyperledger.besu.internal:rlp:${versions.besu}"
+ implementation "org.hyperledger.besu:evm:${versions.besu}"
+ implementation "org.hyperledger.besu.internal:core:${versions.besu}"
+ implementation "org.hyperledger.besu.internal:crypto:${versions.besu}"
+
+ implementation "com.fasterxml.jackson.core:jackson-annotations:${versions.jackson}"
+ implementation "com.fasterxml.jackson.core:jackson-databind:${versions.jackson}"
+ implementation "com.fasterxml.jackson.module:jackson-module-kotlin:${versions.jackson}"
+ implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:${versions.jackson}")
+
+ testImplementation "io.vertx:vertx-junit5:${versions.vertx}"
+ testImplementation "com.github.tomakehurst:wiremock-jre8:${versions.test.wiremock}"
+ testImplementation "org.apache.logging.log4j:log4j-slf4j2-impl:2.20.0"
+}
+
+application {
+ mainClass = 'net.consensys.zkevm.coordinator.app.CoordinatorAppMain'
+}
+
+jar {
+ archiveBaseName = 'coordiantor'
+ dependsOn configurations.runtimeClasspath
+ manifest {
+ attributes(
+ 'Class-Path': configurations.runtimeClasspath.collect { it.getName() }.findAll { it.endsWith('jar') }.join(' '),
+ 'Main-Class': 'net.consensys.zkevm.coordinator.app.CoordinatorAppMain',
+ 'Multi-Release': 'true'
+ )
+ }
+}
+
+distributions {
+ main {
+ distributionBaseName = 'coordinator'
+ }
+}
+
+run {
+ workingDir = rootProject.projectDir
+ jvmArgs = [
+ "-Dvertx.configurationFile=config/coordinator/vertx.properties",
+ "-Dlog4j2.configurationFile=config/coordinator/log4j2-dev.xml"
+ ] + System.properties.entrySet()
+ .findAll { it.key.startsWith("config") }
+ .collect { "-D${it.key}=${it.value}" }
+ args = ["--traces-limits", "config/common/traces-limits-v1.toml", "config/coordinator/coordinator-docker.config.toml", "config/coordinator/coordinator-local-dev.config.overrides.toml"]
+}
+
+sourceSets {
+ integrationTest {
+ kotlin {
+ compileClasspath += main.output
+ runtimeClasspath += main.output
+ }
+ java {
+ compileClasspath += main.output
+ runtimeClasspath += main.output
+ }
+ compileClasspath += sourceSets.main.output + sourceSets.main.compileClasspath + sourceSets.test.compileClasspath
+ runtimeClasspath += sourceSets.main.output + sourceSets.main.runtimeClasspath + sourceSets.test.runtimeClasspath
+ }
+}
+
+tasks.register('integrationTest', Test) {
+ test ->
+ testLogging {
+ events TestLogEvent.FAILED,
+ TestLogEvent.SKIPPED,
+ TestLogEvent.STANDARD_ERROR,
+ TestLogEvent.STARTED
+ exceptionFormat TestExceptionFormat.FULL
+ showCauses true
+ showExceptions true
+ showStackTraces true
+ showStandardStreams true
+ }
+ description = "Runs integration tests."
+ group = "verification"
+ useJUnitPlatform()
+
+ doFirst {
+ def zkEvmV2Address = deployZkevmV2()
+ def l2MessageServiceAddress = deployL2MessageService()
+ test.systemProperty("ZkEvmV2Address", zkEvmV2Address)
+ test.systemProperty("L2MessageService", l2MessageServiceAddress)
+ }
+
+ classpath = sourceSets.integrationTest.runtimeClasspath
+ testClassesDirs = sourceSets.integrationTest.output.classesDirs
+ dependsOn(composeUp)
+}
+
+String deployZkevmV2() {
+ // Container can start afresh and this file becomes outdated and causes issues
+ delete(project.rootProject.file("./contracts/.openzeppelin/unknown-31648428.json"))
+ println("Running zkevm deployment scripts")
+
+ Map env = Map.of("VERIFIER_CONTRACT_NAME", "IntegrationTestTrueVerifier")
+ String output = runRootMakefileCommand("deploy-zkevm2-to-local", env)
+ def deploymentAddressLine = output.split("\n").find { it.startsWith("ZkEvmV2 deployed at ") }
+ Pattern pattern = Pattern.compile("^ZkEvmV2 deployed at (.*)\$");
+ Matcher matcher = pattern.matcher(deploymentAddressLine)
+
+ if (!matcher.find()) {
+ throw new IllegalStateException("Couldn't extract ZkEvmV2 address from the output: $output")
+ } else {
+ return matcher.group(1)
+ }
+}
+
+String deployL2MessageService() {
+ // Container can start afresh and this file becomes outdated and causes issues
+ delete(project.rootProject.file("./contracts/.openzeppelin/unknown-1337.json"))
+ println("Running l2 message service deployment scripts")
+
+ String output = runRootMakefileCommand("deploy-l2messageservice-to-local", Map.of())
+ def deploymentAddressLine = output.split("\n").find { it.startsWith("L2MessageService deployed at ") }
+ Pattern pattern = Pattern.compile("^L2MessageService deployed at (.*)\$");
+ Matcher matcher = pattern.matcher(deploymentAddressLine);
+
+ if (!matcher.find()) {
+ throw new IllegalStateException("Couldn't extract L2MessageService address from the output: $output")
+ } else {
+ return matcher.group(1)
+ }
+}
+
+String runRootMakefileCommand(String command, Map env) {
+ File outputFile = file("output.txt")
+ def deploymentProcessBuilder = new ProcessBuilder("make", "-C", project.rootDir.path, command)
+ deploymentProcessBuilder.environment().putAll(env)
+ deploymentProcessBuilder.redirectOutput(outputFile)
+ deploymentProcessBuilder.redirectError(outputFile)
+ def deploymentProcess = deploymentProcessBuilder.start()
+ def deploymentResult = deploymentProcess.waitFor(4, TimeUnit.MINUTES)
+ def output = outputFile.text
+ outputFile.delete()
+ println(output)
+ if (!deploymentResult) {
+ throw new GradleException("Deployment timed out")
+ }
+ return output
+}
+
+dockerCompose {
+ startedServices = [
+ "postgres",
+ "sequencer",
+ "l1-node",
+ "l1-validator",
+ "l2-node",
+ // For debug
+// "l1-blockscout",
+// "l2-blockscout"
+ ]
+ useComposeFiles = ["${project.rootDir.path}/docker/compose.yml"]
+ environment.putAll(Map.of(
+ "POSTGRES_DB", "coordinator_tests",
+ "POSTGRES_USER", "coordinator",
+ "POSTGRES_PUBLISH_PORT", "6543",
+ "POSTGRES_PASSWORD", "coordinator_tests"))
+ waitForHealthyStateTimeout = Duration.ofMinutes(2)
+ waitForTcpPorts = false
+ removeOrphans = true
+
+ projectName = "docker"
+}
+
+check.finalizedBy("integrationTest")
diff --git a/coordinator/app/src/integrationTest/kotlin/net/consensys/zkevm/ethereum/coordination/dynamicgasprice/DynamicGasPriceServiceIntegrationTest.kt b/coordinator/app/src/integrationTest/kotlin/net/consensys/zkevm/ethereum/coordination/dynamicgasprice/DynamicGasPriceServiceIntegrationTest.kt
new file mode 100644
index 000000000..54ca0de67
--- /dev/null
+++ b/coordinator/app/src/integrationTest/kotlin/net/consensys/zkevm/ethereum/coordination/dynamicgasprice/DynamicGasPriceServiceIntegrationTest.kt
@@ -0,0 +1,341 @@
+package net.consensys.zkevm.ethereum.coordination.dynamicgasprice
+
+import io.micrometer.core.instrument.simple.SimpleMeterRegistry
+import io.vertx.core.Vertx
+import io.vertx.junit5.Timeout
+import io.vertx.junit5.VertxExtension
+import io.vertx.junit5.VertxTestContext
+import net.consensys.linea.contract.AsyncFriendlyTransactionManager
+import net.consensys.linea.jsonrpc.client.VertxHttpJsonRpcClientFactory
+import net.consensys.linea.toHexString
+import net.consensys.linea.toULong
+import org.apache.logging.log4j.LogManager
+import org.apache.logging.log4j.Logger
+import org.assertj.core.api.Assertions
+import org.junit.jupiter.api.Disabled
+import org.junit.jupiter.api.MethodOrderer
+import org.junit.jupiter.api.Order
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.TestMethodOrder
+import org.junit.jupiter.api.extension.ExtendWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+import org.web3j.crypto.Credentials
+import org.web3j.protocol.Web3j
+import org.web3j.protocol.core.DefaultBlockParameterName
+import org.web3j.protocol.core.methods.response.EthBlockNumber
+import org.web3j.protocol.core.methods.response.EthFeeHistory
+import org.web3j.protocol.http.HttpService
+import org.web3j.tx.gas.DefaultGasProvider
+import tech.pegasys.teku.infrastructure.async.SafeFuture
+import java.math.BigInteger
+import java.net.URL
+import java.util.concurrent.TimeUnit
+import kotlin.time.Duration.Companion.seconds
+
+@ExtendWith(VertxExtension::class)
+@TestMethodOrder(MethodOrderer.OrderAnnotation::class)
+@Disabled("FIXME: These tests are flaky and need to be fixed")
+class DynamicGasPriceServiceIntegrationTest {
+ private val log: Logger = LogManager.getLogger(this::class.java)
+ private val meterRegistry = SimpleMeterRegistry()
+ private val pollingInterval = 2.seconds
+ private val feeHistoryBlockCount = 10u
+ private val feeHistoryRewardPercentile = 15.0
+ private val baseFeeCoefficient = 0.1
+ private val priorityFeeCoefficient = 0.1
+ private val initialReward = 1000000000uL
+ private val initialBaseFeePerGas = 10000000000uL
+ private val initialGasUsedRatio = 50u
+ private val l2GasPriceCap = BigInteger("fffffffffffffffff", 16)
+ private val l2ValidatorRpcEndpoint = "http://localhost:8545"
+ private val l2NodeRpcEndpoint = "http://localhost:8845"
+ private val minerGasPriceUpdateRecipients = listOf(l2ValidatorRpcEndpoint, l2NodeRpcEndpoint)
+ private val l1Web3jClient = Web3j.build(HttpService("http://localhost:8445"))
+
+ private val l2ValidatorWeb3jClient = Web3j.build(HttpService(l2ValidatorRpcEndpoint))
+ private val l2NodeWeb3jClient = Web3j.build(HttpService(l2NodeRpcEndpoint))
+
+ // WARNING: FOR LOCAL DEV ONLY - DO NOT REUSE THESE KEYS ELSEWHERE
+ private val l2TestAccountPrivateKey1 = "0x7303d2fadd895018075cbe76d8a700bc65b4a1b8641b97d660533f0e029e3954"
+ private val l2TestAccountPrivateKey2 = "0xc5453712de35e7dc2c599b5f86df5d4f0de442d86a2865cfe557acd6d131aa6f"
+ private val l2Credentials1 = Credentials.create(l2TestAccountPrivateKey1)
+ private val l2Credentials2 = Credentials.create(l2TestAccountPrivateKey2)
+ private val l2ChainId = l2ValidatorWeb3jClient.ethChainId().send().chainId.toLong()
+ private val l2ValidatorTxManager = AsyncFriendlyTransactionManager(l2ValidatorWeb3jClient, l2Credentials1, l2ChainId)
+ private val l2NodeTxManager = AsyncFriendlyTransactionManager(l2NodeWeb3jClient, l2Credentials1, l2ChainId)
+ private val l2NodeTxManager2 = AsyncFriendlyTransactionManager(l2NodeWeb3jClient, l2Credentials2, l2ChainId)
+
+ // Un comment to log Web3j requests and responses
+ // init {
+ // Configurator.setLevel(HttpService::class.java,Level.DEBUG)
+ // }
+ companion object {
+ var sendTxnHash = ""
+ }
+
+ @Test
+ @Order(1)
+ @Timeout(5, timeUnit = TimeUnit.MINUTES)
+ fun `miner set gas price are sent to recipients correctly and underpriced txn is pending`(
+ vertx: Vertx,
+ testContext: VertxTestContext
+ ) {
+ // we need this mocked web3j client because the gas fee history in layer 1 is full of zeros initially
+ var l1LatestBlockNumber = BigInteger.valueOf(2)
+ val l1Web3jClientMock = mock(defaultAnswer = Mockito.RETURNS_DEEP_STUBS)
+ whenever(
+ l1Web3jClientMock
+ .ethFeeHistory(
+ ArgumentMatchers.eq(feeHistoryBlockCount.toInt()),
+ ArgumentMatchers.eq(DefaultBlockParameterName.LATEST),
+ ArgumentMatchers.eq(listOf(feeHistoryRewardPercentile))
+ )
+ .sendAsync()
+ )
+ .thenAnswer {
+ // The following should yield l2 calculated gas fee as 2016233773
+ val feeHistoryResponse = getMockedEthFeeHistory(
+ l1LatestBlockNumber.toULong() - feeHistoryBlockCount,
+ initialReward,
+ initialBaseFeePerGas,
+ initialGasUsedRatio,
+ feeHistoryBlockCount
+ )
+ SafeFuture.completedFuture(feeHistoryResponse)
+ }
+ whenever(
+ l1Web3jClientMock
+ .ethBlockNumber()
+ .sendAsync()
+ )
+ .thenAnswer {
+ val ethBlockNumber = EthBlockNumber()
+ val l1Response = l1Web3jClient.ethBlockNumber().send()
+ ethBlockNumber.result = l1Response.result
+ l1LatestBlockNumber = l1Response.blockNumber
+ SafeFuture.completedFuture(ethBlockNumber)
+ }
+
+ val dynamicGasPriceService = initialiseServices(vertx, l1Web3jClientMock, BigInteger.valueOf(1000000000))
+
+ val l2ValidatorGasPrice = l2ValidatorWeb3jClient.ethGasPrice().send().gasPrice // should be 1000000000
+ val l2NodeGasPrice = l2NodeWeb3jClient.ethGasPrice().send().gasPrice // should be 1000000000
+
+ dynamicGasPriceService.start()
+ .thenApply {
+ vertx.setTimer(pollingInterval.inWholeMilliseconds * 2) {
+ dynamicGasPriceService.stop().thenApply {
+ val updatedL2ValidatorGasPrice =
+ l2ValidatorWeb3jClient.ethGasPrice().send().gasPrice // should see 2016233773
+ val updatedL2NodeGasPrice = l2NodeWeb3jClient.ethGasPrice().send().gasPrice // should see 2016233773
+ testContext.verify {
+ Assertions.assertThat(updatedL2ValidatorGasPrice).isGreaterThan(l2ValidatorGasPrice)
+ Assertions.assertThat(updatedL2NodeGasPrice).isGreaterThan(l2NodeGasPrice)
+ }
+
+ val sendResp = l2ValidatorTxManager.sendEIP1559Transaction(
+ l2ChainId,
+ updatedL2ValidatorGasPrice.subtract(BigInteger.valueOf(1)),
+ updatedL2ValidatorGasPrice.subtract(BigInteger.valueOf(1)),
+ DefaultGasProvider().gasLimit,
+ l2ValidatorTxManager.fromAddress,
+ "0x",
+ BigInteger.valueOf(1000),
+ false
+ )
+ // save the txn hash in the static variable to be retrieved in subsequent tests
+ sendTxnHash = sendResp.transactionHash
+ if (sendTxnHash == null) {
+ // if node returns transaction already known it won't have hash
+ testContext.failNow("Transaction hash is null")
+ }
+ vertx.setTimer(pollingInterval.inWholeMilliseconds * 2) {
+ // should see exception as the underpriced txn would be keeping in txn pool
+ testContext.verify {
+ val receipt = l2ValidatorWeb3jClient.ethGetTransactionReceipt(sendResp.transactionHash).send()
+ Assertions.assertThat(receipt.transactionReceipt.isPresent).isFalse()
+ }.completeNow()
+ }
+ }
+ }
+ }.whenException(testContext::failNow)
+ }
+
+ @Test
+ @Order(2)
+ @Timeout(90, timeUnit = TimeUnit.SECONDS)
+ fun `underpriced txn is mined after miner gas price set to a lower value`(
+ vertx: Vertx,
+ testContext: VertxTestContext
+ ) {
+ var l1LatestBlockNumber = BigInteger.valueOf(2)
+ val l1Web3jClientMock = mock(defaultAnswer = Mockito.RETURNS_DEEP_STUBS)
+ whenever(
+ l1Web3jClientMock
+ .ethFeeHistory(
+ ArgumentMatchers.eq(feeHistoryBlockCount.toInt()),
+ ArgumentMatchers.eq(DefaultBlockParameterName.LATEST),
+ ArgumentMatchers.eq(listOf(feeHistoryRewardPercentile))
+ )
+ .sendAsync()
+ )
+ .thenAnswer {
+ // The following should yield l2 calculated gas fee as 201623383
+ val feeHistoryResponse = getMockedEthFeeHistory(
+ l1LatestBlockNumber.toULong() - feeHistoryBlockCount,
+ (initialReward / 10u),
+ (initialBaseFeePerGas / 10u),
+ initialGasUsedRatio,
+ feeHistoryBlockCount
+ )
+ SafeFuture.completedFuture(feeHistoryResponse)
+ }
+ whenever(
+ l1Web3jClientMock
+ .ethBlockNumber()
+ .sendAsync()
+ )
+ .thenAnswer {
+ val ethBlockNumber = EthBlockNumber()
+ val l1Response = l1Web3jClient.ethBlockNumber().send()
+ ethBlockNumber.result = l1Response.result
+ l1LatestBlockNumber = l1Response.blockNumber
+ SafeFuture.completedFuture(ethBlockNumber)
+ }
+
+ val dynamicGasPriceService = initialiseServices(vertx, l1Web3jClientMock)
+
+ val l2ValidatorGasPrice = l2ValidatorWeb3jClient.ethGasPrice().send().gasPrice // should be 2016233773
+ val l2NodeGasPrice = l2NodeWeb3jClient.ethGasPrice().send().gasPrice // should be 2016233773
+
+ dynamicGasPriceService.start()
+ .thenApply {
+ vertx.setTimer(pollingInterval.inWholeMilliseconds * 2) {
+ dynamicGasPriceService.stop().thenApply {
+ sendTransactionWithGasPrice(l2ValidatorGasPrice)
+ val updatedL2ValidatorGasPrice =
+ l2ValidatorWeb3jClient.ethGasPrice().send().gasPrice // should see 201623383
+ val updatedL2NodeGasPrice = l2NodeWeb3jClient.ethGasPrice().send().gasPrice // should see 201623383
+ testContext.verify {
+ Assertions.assertThat(updatedL2ValidatorGasPrice).isLessThan(l2ValidatorGasPrice)
+ Assertions.assertThat(updatedL2NodeGasPrice).isLessThan(l2NodeGasPrice)
+ }
+ vertx.setTimer(pollingInterval.inWholeMilliseconds * 4) {
+ testContext.verify {
+ val receipt = l2ValidatorWeb3jClient.ethGetTransactionReceipt(sendTxnHash).send()
+ Assertions.assertThat(receipt.transactionReceipt.get().status.equals("0x1"))
+ }.completeNow()
+ }
+ }
+ }
+ }.whenException(testContext::failNow)
+ }
+
+ @Test
+ @Order(3)
+ @Timeout(90, timeUnit = TimeUnit.SECONDS)
+ fun `txn with max fee per gas as current gas price is sent to l2-node and is mined correctly`(
+ vertx: Vertx,
+ testContext: VertxTestContext
+ ) {
+ val l2ValidatorGasPrice = l2ValidatorWeb3jClient.ethGasPrice().send().gasPrice
+
+ vertx.setTimer(pollingInterval.inWholeMilliseconds * 2) {
+ val sendResp = l2NodeTxManager.sendEIP1559Transaction(
+ l2ChainId,
+ l2ValidatorGasPrice,
+ l2ValidatorGasPrice,
+ DefaultGasProvider().gasLimit,
+ l2NodeTxManager.fromAddress,
+ "0x",
+ BigInteger.valueOf(1000),
+ false
+ )
+ sendTxnHash = sendResp.transactionHash
+
+ vertx.setTimer(pollingInterval.inWholeMilliseconds * 3) {
+ testContext.verify {
+ val receipt = l2NodeWeb3jClient.ethGetTransactionReceipt(sendTxnHash).send()
+ Assertions.assertThat(receipt.transactionReceipt.get().status.equals("0x1"))
+ }.completeNow()
+ }
+ }
+ }
+
+ private fun sendTransactionWithGasPrice(gasPrice: BigInteger) {
+ l2NodeTxManager2.sendTransaction(
+ gasPrice,
+ BigInteger.valueOf(25000),
+ l2Credentials2.address,
+ "",
+ BigInteger.ZERO
+ )
+ }
+
+ private fun getMockedEthFeeHistory(
+ oldestBlockNumber: ULong,
+ initialReward: ULong,
+ initialBaseFeePerGas: ULong,
+ initialGasUsedRatio: UInt,
+ feeHistoryBlockCount: UInt
+ ): EthFeeHistory {
+ val feeHistoryResponse = EthFeeHistory()
+ val feeHistory = EthFeeHistory.FeeHistory()
+ feeHistory.setReward((initialReward until initialReward + feeHistoryBlockCount).map { listOf(it.toString()) })
+ feeHistory.setBaseFeePerGas(
+ (initialBaseFeePerGas until initialBaseFeePerGas + feeHistoryBlockCount + 1u)
+ .map { it.toString() }
+ )
+ feeHistory.gasUsedRatio =
+ (initialGasUsedRatio until initialGasUsedRatio + feeHistoryBlockCount).map { it.toDouble() / 100.0 }
+ feeHistory.setOldestBlock(oldestBlockNumber.toHexString())
+ feeHistoryResponse.result = feeHistory
+ return feeHistoryResponse
+ }
+
+ private fun initialiseServices(
+ vertx: Vertx,
+ l1Web3jClient: Web3j,
+ initialGasPrice: BigInteger? = null
+ ): DynamicGasPriceService {
+ val feesFetcher: FeesFetcher = FeeHistoryFetcherImpl(
+ l1Web3jClient,
+ FeeHistoryFetcherImpl.Config(
+ feeHistoryBlockCount,
+ feeHistoryRewardPercentile
+ )
+ )
+
+ val l2MinMinerTipCalculator: FeesCalculator = GasUsageRatioWeightedAverageFeesCalculator(
+ GasUsageRatioWeightedAverageFeesCalculator.Config(
+ baseFeeCoefficient.toBigDecimal(),
+ priorityFeeCoefficient.toBigDecimal()
+ )
+ )
+
+ val l2SetGasPriceUpdater: GasPriceUpdater = GasPriceUpdaterImpl(
+ VertxHttpJsonRpcClientFactory(vertx, meterRegistry),
+ GasPriceUpdaterImpl.Config(
+ minerGasPriceUpdateRecipients.map { URL(it) }
+ )
+ )
+
+ if (initialGasPrice != null) {
+ l2SetGasPriceUpdater.updateMinerGasPrice(initialGasPrice).get()
+ }
+
+ return DynamicGasPriceService(
+ DynamicGasPriceService.Config(
+ pollingInterval,
+ l2GasPriceCap
+ ),
+ vertx,
+ feesFetcher,
+ l2MinMinerTipCalculator,
+ l2SetGasPriceUpdater
+ )
+ }
+}
diff --git a/coordinator/app/src/integrationTest/kotlin/net/consensys/zkevm/ethereum/coordination/messageanchoring/L1EventQuerierIntegrationTest.kt b/coordinator/app/src/integrationTest/kotlin/net/consensys/zkevm/ethereum/coordination/messageanchoring/L1EventQuerierIntegrationTest.kt
new file mode 100644
index 000000000..d43e42829
--- /dev/null
+++ b/coordinator/app/src/integrationTest/kotlin/net/consensys/zkevm/ethereum/coordination/messageanchoring/L1EventQuerierIntegrationTest.kt
@@ -0,0 +1,237 @@
+package net.consensys.zkevm.ethereum.coordination.messageanchoring
+
+import io.vertx.core.Vertx
+import io.vertx.junit5.Timeout
+import io.vertx.junit5.VertxExtension
+import io.vertx.junit5.VertxTestContext
+import net.consensys.linea.contract.AsyncFriendlyTransactionManager
+import net.consensys.linea.contract.ZkEvmV2
+import net.consensys.linea.contract.ZkEvmV2AsyncFriendly
+import org.apache.tuweni.bytes.Bytes32
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.Disabled
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.web3j.abi.EventEncoder
+import org.web3j.crypto.Credentials
+import org.web3j.protocol.Web3j
+import org.web3j.protocol.http.HttpService
+import org.web3j.tx.gas.DefaultGasProvider
+import org.web3j.tx.response.PollingTransactionReceiptProcessor
+import org.web3j.utils.Async
+import java.math.BigInteger
+import java.util.*
+import java.util.concurrent.TimeUnit
+import kotlin.time.Duration.Companion.seconds
+
+@ExtendWith(VertxExtension::class)
+class L1EventQuerierIntegrationTest {
+ private val testZkEvmContractAddress = System.getProperty("ZkEvmV2Address")
+ private val l2RecipientAddress = "0x03dfa322A95039BB679771346Ee2dBfEa0e2B773"
+ private val l1RpcEndpoint = "http://localhost:8445"
+
+ // WARNING: FOR LOCAL DEV ONLY - DO NOT REUSE THESE KEYS ELSEWHERE
+ private val testAccountPrivateKey = "202454d1b4e72c41ebf58150030f649648d3cf5590297fb6718e27039ed9c86d"
+ private val blockRangeLoopLimit = 5u
+ private val maxMessagesToCollect = 100u
+
+ data class L1MessageToSend(
+ val recipient: String,
+ val fee: BigInteger,
+ val calldata: ByteArray,
+ val value: BigInteger
+ ) {
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as L1MessageToSend
+
+ if (recipient != other.recipient) return false
+ if (fee != other.fee) return false
+ if (!calldata.contentEquals(other.calldata)) return false
+ return value == other.value
+ }
+
+ override fun hashCode(): Int {
+ var result = recipient.hashCode()
+ result = 31 * result + fee.hashCode()
+ result = 31 * result + calldata.contentHashCode()
+ result = 31 * result + value.hashCode()
+ return result
+ }
+ }
+
+ private fun connectToZkevmContract(web3Client: Web3j): ZkEvmV2AsyncFriendly {
+ val credentials = Credentials.create(testAccountPrivateKey)
+ val chainId = web3Client.ethChainId().send().chainId.toLong()
+ val pollingTransactionReceiptProcessor = PollingTransactionReceiptProcessor(web3Client, 1000, 40)
+ val transactionManager = AsyncFriendlyTransactionManager(
+ web3Client,
+ credentials,
+ chainId,
+ pollingTransactionReceiptProcessor
+ )
+
+ return ZkEvmV2AsyncFriendly.load(testZkEvmContractAddress, web3Client, transactionManager, DefaultGasProvider())
+ }
+
+ @Test
+ @Timeout(45, timeUnit = TimeUnit.SECONDS)
+ fun `l1Event querier returns events from the given hash`(vertx: Vertx, testContext: VertxTestContext) {
+ val web3Client = Web3j.build(HttpService(l1RpcEndpoint), 1000, Async.defaultExecutorService())
+ val contract = connectToZkevmContract(web3Client)
+
+ val baseMessageToSend =
+ L1MessageToSend(l2RecipientAddress, BigInteger.ZERO, ByteArray(0), BigInteger.valueOf(100001))
+ val messagesToSend = listOf(
+ baseMessageToSend,
+ baseMessageToSend.copy(value = BigInteger.valueOf(100001)),
+ baseMessageToSend.copy(value = BigInteger.valueOf(100002)),
+ baseMessageToSend.copy(value = BigInteger.valueOf(100003)),
+ baseMessageToSend.copy(value = BigInteger.valueOf(100005))
+ )
+
+ val earlierEmittedParallelEvents = messagesToSend.map {
+ contract.sendMessage(it.recipient, it.fee, it.calldata, it.value).sendAsync()
+ .thenApply { receipt ->
+ Pair(
+ ZkEvmV2.staticExtractEventParameters(
+ ZkEvmV2.MESSAGESENT_EVENT,
+ receipt.logs.first { log ->
+ log.topics.contains(EventEncoder.encode(ZkEvmV2.MESSAGESENT_EVENT))
+ }
+ ),
+ receipt
+ )
+ }
+ }
+
+ val earlierEvents = earlierEmittedParallelEvents.map { it.get() }
+
+ val hashIndexToQueryFrom = 2
+ val hashInTheMiddle = earlierEvents[hashIndexToQueryFrom].let {
+ Bytes32.wrap(it.first.indexedValues[2].value as ByteArray)
+ }
+
+ val laterEmittedEvents = messagesToSend.map {
+ contract.sendMessage(it.recipient, it.fee, it.calldata, it.value).sendAsync()
+ .thenApply { receipt ->
+ Pair(
+ ZkEvmV2.staticExtractEventParameters(
+ ZkEvmV2.MESSAGESENT_EVENT,
+ receipt.logs.first { log ->
+ log.topics.contains(EventEncoder.encode(ZkEvmV2.MESSAGESENT_EVENT))
+ }
+ ),
+ receipt
+ )
+ }
+ }
+
+ val laterEvents = laterEmittedEvents.map { it.get() }
+ val multiPostBlockLoopLimit = 20u
+ val l1QuerierImpl = L1EventQuerierImpl(
+ vertx,
+ L1EventQuerierImpl.Config(
+ 2.seconds,
+ 16.seconds,
+ BigInteger.ZERO,
+ maxMessagesToCollect,
+ testZkEvmContractAddress,
+ "latest",
+ multiPostBlockLoopLimit
+ ),
+ web3Client
+ )
+
+ // we should have events 4 and 5 (count = 5, less hashIndexToQueryFrom, less 1 (0 based))
+ val allExpectedHashesInOrder: MutableList =
+ earlierEvents.drop(hashIndexToQueryFrom + 1).take(maxMessagesToCollect.toInt()).map {
+ SendMessageEvent(
+ Bytes32.wrap(it.first.indexedValues[2].value as ByteArray)
+ )
+ }.toMutableList()
+
+ l1QuerierImpl.getSendMessageEventsForAnchoredMessage(MessageHashAnchoredEvent(hashInTheMiddle)).thenApply {
+ // we should have events 1,2,3,4 and 5
+ val expectedHashes = laterEvents.map {
+ SendMessageEvent(
+ Bytes32.wrap(it.first.indexedValues[2].value as ByteArray)
+ )
+ }.toMutableList()
+
+ // we should have all 7
+ allExpectedHashesInOrder.addAll(expectedHashes)
+
+ testContext.verify {
+ assertThat(it).isEqualTo(allExpectedHashesInOrder)
+ }.completeNow()
+ }.whenException(testContext::failNow)
+ }
+
+ @Disabled
+ @Timeout(1, timeUnit = TimeUnit.MINUTES)
+ fun `l1Event querier returns events with more than 10000 messages`(vertx: Vertx, testContext: VertxTestContext) {
+ val web3Client = Web3j.build(HttpService(l1RpcEndpoint), 1000, Async.defaultExecutorService())
+ val contract = connectToZkevmContract(web3Client)
+
+ val baseMessageToSend =
+ L1MessageToSend(l2RecipientAddress, BigInteger.ZERO, ByteArray(0), BigInteger.valueOf(100001))
+
+ val messagesToSend = (1..10025).map { baseMessageToSend.copy(value = BigInteger.valueOf(100001)) }
+
+ val earlierEmittedParallelEvents = messagesToSend.map {
+ contract.sendMessage(it.recipient, it.fee, it.calldata, it.value).sendAsync()
+ .thenApply { receipt ->
+ Pair(
+ ZkEvmV2.staticExtractEventParameters(
+ ZkEvmV2.MESSAGESENT_EVENT,
+ receipt.logs.first { log ->
+ log.topics.contains(EventEncoder.encode(ZkEvmV2.MESSAGESENT_EVENT))
+ }
+ ),
+ receipt
+ )
+ }
+ }
+
+ val events = earlierEmittedParallelEvents.map { it.get() }
+
+ val hashIndexToQueryFrom = 2
+ val hashToTake = events[hashIndexToQueryFrom].let {
+ Bytes32.wrap(it.first.indexedValues[2].value as ByteArray)
+ }
+
+ val l1QuerierImpl = L1EventQuerierImpl(
+ vertx,
+ L1EventQuerierImpl.Config(
+ 2.seconds,
+ 16.seconds,
+ BigInteger.ZERO,
+ maxMessagesToCollect,
+ testZkEvmContractAddress,
+ "latest",
+ blockRangeLoopLimit
+ ),
+ web3Client
+ )
+
+ val allExpectedHashesInOrder =
+ events.drop(hashIndexToQueryFrom + 1).take(maxMessagesToCollect.toInt()).map {
+ Bytes32.wrap(it.first.indexedValues[2].value as ByteArray)
+ }
+
+ l1QuerierImpl.getSendMessageEventsForAnchoredMessage(MessageHashAnchoredEvent(hashToTake)).thenApply {
+ val foundHashes = it.map { evt ->
+ evt.messageHash
+ }
+
+ // we should have all the hashes meeting the maxMessagesToCollect
+ testContext.verify {
+ assertThat(it.count()).isEqualTo(maxMessagesToCollect)
+ assertThat(foundHashes).isEqualTo(allExpectedHashesInOrder)
+ }.completeNow()
+ }.whenException(testContext::failNow)
+ }
+}
diff --git a/coordinator/app/src/integrationTest/kotlin/net/consensys/zkevm/ethereum/coordination/messageanchoring/L2MessageAnchorerIntegrationTest.kt b/coordinator/app/src/integrationTest/kotlin/net/consensys/zkevm/ethereum/coordination/messageanchoring/L2MessageAnchorerIntegrationTest.kt
new file mode 100644
index 000000000..f1a914ca1
--- /dev/null
+++ b/coordinator/app/src/integrationTest/kotlin/net/consensys/zkevm/ethereum/coordination/messageanchoring/L2MessageAnchorerIntegrationTest.kt
@@ -0,0 +1,212 @@
+package net.consensys.zkevm.ethereum.coordination.messageanchoring
+
+import io.vertx.core.Vertx
+import io.vertx.junit5.Timeout
+import io.vertx.junit5.VertxExtension
+import io.vertx.junit5.VertxTestContext
+import net.consensys.linea.contract.AsyncFriendlyTransactionManager
+import net.consensys.linea.contract.L2MessageService
+import net.consensys.linea.contract.ZkEvmV2
+import org.apache.tuweni.bytes.Bytes32
+import org.assertj.core.api.Assertions
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.web3j.abi.EventEncoder
+import org.web3j.crypto.Credentials
+import org.web3j.protocol.Web3j
+import org.web3j.protocol.http.HttpService
+import org.web3j.tx.RawTransactionManager
+import org.web3j.tx.gas.DefaultGasProvider
+import org.web3j.tx.response.PollingTransactionReceiptProcessor
+import org.web3j.utils.Async
+import java.math.BigInteger
+import java.util.concurrent.TimeUnit
+import kotlin.time.Duration.Companion.seconds
+
+@ExtendWith(VertxExtension::class)
+class L2MessageAnchorerIntegrationTest {
+ private val testZkEvmContractAddress = System.getProperty("ZkEvmV2Address")
+ private val testL2MessageManagerContractAddress = System.getProperty("L2MessageService")
+ private val l2RecipientAddress = "0x03dfa322A95039BB679771346Ee2dBfEa0e2B773"
+ private val l1RpcEndpoint = "http://127.0.0.1:8445"
+ private val l2RpcEndpoint = "http://127.0.0.1:8545"
+
+ // WARNING: FOR LOCAL DEV ONLY - DO NOT REUSE THESE KEYS ELSEWHERE
+ private val testAccountPrivateKey = "202454d1b4e72c41ebf58150030f649648d3cf5590297fb6718e27039ed9c86d"
+ private val l2TestAccountPrivateKey = "0x1dd171cec7e2995408b5513004e8207fe88d6820aeff0d82463b3e41df251aae"
+ private val blockRangeLoopLimit = 100u
+
+ private val firstMessage =
+ L1EventQuerierIntegrationTest.L1MessageToSend(
+ l2RecipientAddress,
+ BigInteger.TEN,
+ ByteArray(0),
+ BigInteger.valueOf(100001)
+ )
+ private val secondMessage = firstMessage.copy(fee = BigInteger.valueOf(11))
+
+ @Test
+ @Timeout(2, timeUnit = TimeUnit.MINUTES)
+ fun `can send a message on L1 and see the hash on L2`(vertx: Vertx, testContext: VertxTestContext) {
+ val l2Web3jClient = Web3j.build(HttpService(l2RpcEndpoint), 1000, Async.defaultExecutorService())
+ val l2credentials = Credentials.create(l2TestAccountPrivateKey)
+ val l2ChainId = l2Web3jClient.ethChainId().send().chainId.toLong()
+ val pollingTransactionReceiptProcessor = PollingTransactionReceiptProcessor(l2Web3jClient, 1000, 40)
+ val l2TransactionManager = AsyncFriendlyTransactionManager(
+ l2Web3jClient,
+ l2credentials,
+ l2ChainId,
+ pollingTransactionReceiptProcessor
+ )
+
+ val l2Contract = L2MessageService.load(
+ testL2MessageManagerContractAddress,
+ l2Web3jClient,
+ l2TransactionManager,
+ DefaultGasProvider()
+ )
+
+ // we need to wait a sufficient time for block processing
+ val l2MessageAnchorerImpl = L2MessageAnchorerImpl(
+ vertx,
+ l2Web3jClient,
+ l2Contract,
+ L2MessageAnchorerImpl.Config(12.seconds, 5u, 0)
+ )
+
+ val expectedHash = Bytes32.random()
+
+ l2MessageAnchorerImpl.anchorMessages(listOf(expectedHash))
+ .thenCompose { txReceipt ->
+ l2Contract.sendMessage(firstMessage.recipient, firstMessage.fee, firstMessage.calldata, firstMessage.value)
+ .sendAsync()
+ .thenApply { txReceipt }
+ }
+ .thenCompose { txReceipt ->
+ l2Contract.sendMessage(secondMessage.recipient, secondMessage.fee, secondMessage.calldata, secondMessage.value)
+ .sendAsync()
+ .thenApply { txReceipt }
+ }
+ .thenApply { transactionReceipt ->
+ testContext.verify {
+ Assertions.assertThat(transactionReceipt.logs).isNotNull
+ Assertions.assertThat(transactionReceipt.logs).isNotEmpty
+ Assertions.assertThat(transactionReceipt.logs.count()).isEqualTo(1)
+ Assertions.assertThat(transactionReceipt.logs[0].topics[0]).isEqualTo(
+ EventEncoder.encode(L2MessageService.L1L2MESSAGEHASHESADDEDTOINBOX_EVENT)
+ )
+ Assertions.assertThat(transactionReceipt.logs[0].topics.count()).isEqualTo(1)
+ Assertions.assertThat(transactionReceipt.logs[0].data).contains(
+ expectedHash.toString().removePrefix("0x")
+ )
+ Assertions.assertThat(transactionReceipt.blockNumber).isNotNull
+ }.completeNow()
+ }.whenException(testContext::failNow)
+ }
+
+ @Test
+ @Timeout(1, timeUnit = TimeUnit.MINUTES)
+ fun `all hashes found are anchored`(vertx: Vertx, testContext: VertxTestContext) {
+ val l1Web3jClient = Web3j.build(HttpService(l1RpcEndpoint), 1000, Async.defaultExecutorService())
+ val credentials = Credentials.create(testAccountPrivateKey)
+ val chainId = l1Web3jClient.ethChainId().send().chainId.toLong()
+ val l1PollingTransactionReceiptProcessor = PollingTransactionReceiptProcessor(l1Web3jClient, 1000, 40)
+ val transactionManager = RawTransactionManager(
+ l1Web3jClient,
+ credentials,
+ chainId,
+ l1PollingTransactionReceiptProcessor
+ )
+
+ val contract = ZkEvmV2.load(testZkEvmContractAddress, l1Web3jClient, transactionManager, DefaultGasProvider())
+
+ val l2Web3jClient = Web3j.build(HttpService(l2RpcEndpoint), 1000, Async.defaultExecutorService())
+ val l2credentials = Credentials.create(l2TestAccountPrivateKey)
+ val l2ChainId = l2Web3jClient.ethChainId().send().chainId.toLong()
+ val l2PollingTransactionReceiptProcessor = PollingTransactionReceiptProcessor(l2Web3jClient, 1000, 40)
+ val l2TransactionManager = RawTransactionManager(
+ l2Web3jClient,
+ l2credentials,
+ l2ChainId,
+ l2PollingTransactionReceiptProcessor
+ )
+
+ val l2Contract = L2MessageService.load(
+ testL2MessageManagerContractAddress,
+ l2Web3jClient,
+ l2TransactionManager,
+ DefaultGasProvider()
+ )
+
+ val baseMessageToSend =
+ L1EventQuerierIntegrationTest.L1MessageToSend(
+ l2RecipientAddress,
+ BigInteger.TEN,
+ ByteArray(0),
+ BigInteger.valueOf(100001)
+ )
+ val messagesToSend = listOf(
+ baseMessageToSend,
+ baseMessageToSend.copy(fee = BigInteger.valueOf(11)),
+ baseMessageToSend.copy(value = BigInteger.valueOf(100001))
+ )
+ val emittedEvents = messagesToSend.map {
+ contract.sendMessage(it.recipient, it.fee, it.calldata, it.value).send()
+ }.map {
+ ZkEvmV2.staticExtractEventParameters(
+ ZkEvmV2.MESSAGESENT_EVENT,
+ it.logs.first { log ->
+ log.topics.contains(EventEncoder.encode(ZkEvmV2.MESSAGESENT_EVENT))
+ }
+ )
+ }
+
+ val hashInTheMiddle = emittedEvents[1].let {
+ Bytes32.wrap(it.indexedValues[2].value as ByteArray)
+ }
+
+ val l1QuerierImpl = L1EventQuerierImpl(
+ vertx,
+ L1EventQuerierImpl.Config(
+ 2.seconds,
+ 30.seconds,
+ BigInteger.ZERO,
+ 100u,
+ testZkEvmContractAddress,
+ "latest",
+ blockRangeLoopLimit
+ ),
+ l1Web3jClient
+ )
+
+ // we need to wait a sufficient time for block processing
+ val l2MessageAnchorerImpl = L2MessageAnchorerImpl(
+ vertx,
+ l2Web3jClient,
+ l2Contract,
+ L2MessageAnchorerImpl.Config(12.seconds, 5u, 0)
+ )
+
+ val expectedHash = Bytes32.wrap(emittedEvents.last().indexedValues[2].value as ByteArray)
+
+ l1QuerierImpl.getSendMessageEventsForAnchoredMessage(MessageHashAnchoredEvent(hashInTheMiddle))
+ .thenApply { events ->
+ l2MessageAnchorerImpl.anchorMessages(events.map { it.messageHash })
+ .thenApply { transactionReceipt ->
+ testContext.verify {
+ Assertions.assertThat(transactionReceipt.logs).isNotNull
+ Assertions.assertThat(transactionReceipt.logs).isNotEmpty
+ Assertions.assertThat(transactionReceipt.logs.count()).isEqualTo(1)
+ Assertions.assertThat(transactionReceipt.logs[0].topics[0]).isEqualTo(
+ EventEncoder.encode(L2MessageService.L1L2MESSAGEHASHESADDEDTOINBOX_EVENT)
+ )
+ Assertions.assertThat(transactionReceipt.logs[0].topics.count()).isEqualTo(1)
+ Assertions.assertThat(transactionReceipt.logs[0].data).contains(
+ expectedHash.toString().removePrefix("0x")
+ )
+ Assertions.assertThat(transactionReceipt.blockNumber).isNotNull
+ }.completeNow()
+ }
+ }.whenException(testContext::failNow)
+ }
+}
diff --git a/coordinator/app/src/integrationTest/kotlin/net/consensys/zkevm/ethereum/coordination/messageanchoring/L2QuerierIntegrationTest.kt b/coordinator/app/src/integrationTest/kotlin/net/consensys/zkevm/ethereum/coordination/messageanchoring/L2QuerierIntegrationTest.kt
new file mode 100644
index 000000000..3fef5d02c
--- /dev/null
+++ b/coordinator/app/src/integrationTest/kotlin/net/consensys/zkevm/ethereum/coordination/messageanchoring/L2QuerierIntegrationTest.kt
@@ -0,0 +1,80 @@
+package net.consensys.zkevm.ethereum.coordination.messageanchoring
+
+import io.vertx.core.Vertx
+import io.vertx.junit5.Timeout
+import io.vertx.junit5.VertxExtension
+import io.vertx.junit5.VertxTestContext
+import net.consensys.linea.contract.L2MessageService
+import org.apache.tuweni.bytes.Bytes32
+import org.assertj.core.api.Assertions
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.web3j.crypto.Credentials
+import org.web3j.protocol.Web3j
+import org.web3j.protocol.http.HttpService
+import org.web3j.tx.RawTransactionManager
+import org.web3j.tx.gas.DefaultGasProvider
+import org.web3j.tx.response.PollingTransactionReceiptProcessor
+import org.web3j.utils.Async
+import java.math.BigInteger
+import java.util.concurrent.TimeUnit
+import kotlin.time.Duration.Companion.seconds
+
+@ExtendWith(VertxExtension::class)
+class L2QuerierIntegrationTest {
+ private val testL2MessageManagerContractAddress = System.getProperty("L2MessageService")
+ private val l2RpcEndpoint = "http://localhost:8545"
+
+ // WARNING: FOR LOCAL DEV ONLY - DO NOT REUSE THESE KEYS ELSEWHERE
+ private val l2TestAccountPrivateKey = "0x1dd171cec7e2995408b5513004e8207fe88d6820aeff0d82463b3e41df251aae"
+
+ @Test
+ @Timeout(1, timeUnit = TimeUnit.MINUTES)
+ fun `anchored hashes are returned correctly`(vertx: Vertx, testContext: VertxTestContext) {
+ val l2Web3jClient = Web3j.build(HttpService(l2RpcEndpoint), 1000, Async.defaultExecutorService())
+ val l2credentials = Credentials.create(l2TestAccountPrivateKey)
+ val l2ChainId = l2Web3jClient.ethChainId().send().chainId.toLong()
+ val pollingTransactionReceiptProcessor = PollingTransactionReceiptProcessor(l2Web3jClient, 1000, 40)
+ val l2TransactionManager = RawTransactionManager(
+ l2Web3jClient,
+ l2credentials,
+ l2ChainId,
+ pollingTransactionReceiptProcessor
+ )
+
+ val l2Contract = L2MessageService.load(
+ testL2MessageManagerContractAddress,
+ l2Web3jClient,
+ l2TransactionManager,
+ DefaultGasProvider()
+ )
+
+ val config = L2QuerierImpl.Config(0u, 2u, 2u, testL2MessageManagerContractAddress)
+ val l2Querier = L2QuerierImpl(l2Web3jClient, l2Contract, config, vertx)
+ val hashes = createRandomEventWithHashes(10)
+
+ val l2MessageAnchorerImpl = L2MessageAnchorerImpl(
+ vertx,
+ l2Web3jClient,
+ l2Contract,
+ L2MessageAnchorerImpl.Config(12.seconds, 8u, 0)
+ )
+
+ l2MessageAnchorerImpl.anchorMessages(hashes)
+ .thenCompose { _ ->
+ l2Querier.findLastFinalizedAnchoredEvent().thenCompose {
+ l2Querier.getMessageHashStatus(it!!.messageHash).thenApply {
+ testContext.verify {
+ Assertions.assertThat(it).isNotNull
+ Assertions.assertThat(it).isEqualTo(BigInteger.valueOf(1))
+ }.completeNow()
+ }
+ }.whenException(testContext::failNow)
+ }
+ }
+
+ private fun createRandomEventWithHashes(numberOfRandomHashes: Int): List {
+ return (0..numberOfRandomHashes)
+ .map { Bytes32.random() }
+ }
+}
diff --git a/coordinator/app/src/integrationTest/kotlin/net/consensys/zkevm/ethereum/coordination/messageanchoring/MessageServiceIntegrationTest.kt b/coordinator/app/src/integrationTest/kotlin/net/consensys/zkevm/ethereum/coordination/messageanchoring/MessageServiceIntegrationTest.kt
new file mode 100644
index 000000000..15642fe83
--- /dev/null
+++ b/coordinator/app/src/integrationTest/kotlin/net/consensys/zkevm/ethereum/coordination/messageanchoring/MessageServiceIntegrationTest.kt
@@ -0,0 +1,225 @@
+package net.consensys.zkevm.ethereum.coordination.messageanchoring
+
+import io.vertx.core.Vertx
+import io.vertx.junit5.Timeout
+import io.vertx.junit5.VertxExtension
+import io.vertx.junit5.VertxTestContext
+import net.consensys.linea.async.retryWithInterval
+import net.consensys.linea.contract.AsyncFriendlyTransactionManager
+import net.consensys.linea.contract.L2MessageService
+import net.consensys.linea.contract.ZkEvmV2
+import net.consensys.zkevm.ethereum.EIP1559GasProvider
+import org.apache.logging.log4j.LogManager
+import org.apache.logging.log4j.Logger
+import org.apache.tuweni.bytes.Bytes32
+import org.assertj.core.api.Assertions
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.web3j.abi.EventEncoder
+import org.web3j.crypto.Credentials
+import org.web3j.protocol.Web3j
+import org.web3j.protocol.http.HttpService
+import org.web3j.tx.gas.DefaultGasProvider
+import org.web3j.tx.response.PollingTransactionReceiptProcessor
+import org.web3j.utils.Async
+import tech.pegasys.teku.infrastructure.async.SafeFuture
+import java.math.BigInteger
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.TimeUnit
+import kotlin.time.Duration.Companion.seconds
+
+@ExtendWith(VertxExtension::class)
+class MessageServiceIntegrationTest {
+ private val log: Logger = LogManager.getLogger(this::class.java)
+
+ private val testZkEvmContractAddress = System.getProperty("ZkEvmV2Address")
+ private val testL2MessageManagerContractAddress = System.getProperty("L2MessageService")
+ private val l2RecipientAddress = "0x03dfa322A95039BB679771346Ee2dBfEa0e2B773"
+ private val l1RpcEndpoint = "http://localhost:8445"
+ private val l2RpcEndpoint = "http://localhost:8545"
+
+ // WARNING: FOR LOCAL DEV ONLY - DO NOT REUSE THESE KEYS ELSEWHERE
+ private val testAccountPrivateKey = "202454d1b4e72c41ebf58150030f649648d3cf5590297fb6718e27039ed9c86d"
+ private val l2TestAccountPrivateKey = "0x1dd171cec7e2995408b5513004e8207fe88d6820aeff0d82463b3e41df251aae"
+
+ private val l1Web3Client = Web3j.build(HttpService(l1RpcEndpoint), 1000, Async.defaultExecutorService())
+ private val credentials = Credentials.create(testAccountPrivateKey)
+ private val chainId = l1Web3Client.ethChainId().send().chainId.toLong()
+ private val l1PollingTransactionReceiptProcessor = PollingTransactionReceiptProcessor(l1Web3Client, 1000, 40)
+ private val transactionManager = AsyncFriendlyTransactionManager(
+ l1Web3Client,
+ credentials,
+ chainId,
+ l1PollingTransactionReceiptProcessor
+ )
+ private val contract = ZkEvmV2.load(testZkEvmContractAddress, l1Web3Client, transactionManager, DefaultGasProvider())
+
+ private val l2Web3jClient = Web3j.build(HttpService(l2RpcEndpoint), 1000, Async.defaultExecutorService())
+ private val l2credentials = Credentials.create(l2TestAccountPrivateKey)
+ private val l2ChainId = l2Web3jClient.ethChainId().send().chainId.toLong()
+ private val l2PollingTransactionReceiptProcessor = PollingTransactionReceiptProcessor(l2Web3jClient, 1000, 40)
+ private val l2TransactionManager = AsyncFriendlyTransactionManager(
+ l2Web3jClient,
+ l2credentials,
+ l2ChainId,
+ l2PollingTransactionReceiptProcessor
+ )
+
+ private val l2Contract = L2MessageService.load(
+ testL2MessageManagerContractAddress,
+ l2Web3jClient,
+ l2TransactionManager,
+ DefaultGasProvider()
+ )
+
+ private val pollingInterval = 2.seconds
+ private val sendMessagePollingInterval = 2.seconds
+ private val maxScrapingTime = 30.seconds
+ private val earliestBlock = BigInteger.valueOf(0)
+ private val maxMessagesToAnchor = 5u
+ private val blockRangeLoopLimit = 100u
+ private val receiptPollingInterval = 2.seconds
+
+ private val messageAnchoringServiceConfig = MessageAnchoringService.Config(
+ pollingInterval,
+ maxMessagesToAnchor
+ )
+
+ @Test
+ @Timeout(1, timeUnit = TimeUnit.MINUTES)
+ fun `anchored hashes are returned correctly`(vertx: Vertx, testContext: VertxTestContext) {
+ val messageAnchoringService = initialiseServices(vertx)
+
+ val sentMessages = sendMessages()
+ messageAnchoringService.start()
+ .thenCompose { _ ->
+ allHashesHaveBeenSet(sentMessages, 60, vertx).thenCompose { success ->
+ messageAnchoringService.stop().thenApply {
+ testContext.verify {
+ Assertions.assertThat(success).isTrue()
+ }.completeNow()
+ }
+ }
+ }.whenException(testContext::failNow)
+ }
+
+ private fun initialiseServices(vertx: Vertx): MessageAnchoringService {
+ val l2MessageService =
+ L2MessageService.load(
+ testL2MessageManagerContractAddress,
+ l2Web3jClient,
+ l2TransactionManager,
+ EIP1559GasProvider(
+ l2Web3jClient,
+ EIP1559GasProvider.Config(
+ BigInteger.valueOf(10000000),
+ BigInteger.valueOf(100000000000),
+ 4u,
+ 0.5
+ )
+ )
+ )
+
+ val l1EventQuerier = L1EventQuerierImpl(
+ vertx,
+ L1EventQuerierImpl.Config(
+ sendMessagePollingInterval,
+ maxScrapingTime,
+ earliestBlock,
+ maxMessagesToAnchor,
+ testZkEvmContractAddress,
+ "latest",
+ blockRangeLoopLimit
+ ),
+ l1Web3Client
+ )
+
+ val l2Querier = L2QuerierImpl(
+ l2Web3jClient,
+ l2MessageService,
+ L2QuerierImpl.Config(
+ 0u,
+ 5u,
+ 2u,
+ testL2MessageManagerContractAddress
+ ),
+ vertx
+ )
+
+ val l2MessageAnchorer: L2MessageAnchorer = L2MessageAnchorerImpl(
+ vertx,
+ l2Web3jClient,
+ l2MessageService,
+ L2MessageAnchorerImpl.Config(
+ receiptPollingInterval,
+ 10u,
+ 0
+ )
+ )
+
+ return MessageAnchoringService(
+ messageAnchoringServiceConfig,
+ vertx,
+ l1EventQuerier,
+ l2MessageAnchorer,
+ l2Querier,
+ l2Contract,
+ l2TransactionManager
+ )
+ }
+
+ private fun allHashesHaveBeenSet(
+ messageHashes: List,
+ maxAttempts: Int,
+ vertx: Vertx
+ ): SafeFuture {
+ return retryWithInterval(maxAttempts, 2.seconds, vertx, { it }) {
+ val statuses: List> = messageHashes.map { messageHash ->
+ l2Contract.inboxL1L2MessageStatus(messageHash.toArray()).sendAsync()
+ .thenApply {
+ it == BigInteger.valueOf(1)
+ }
+ }
+ statuses.fold(SafeFuture.completedFuture(true)) { acc, newFuture ->
+ acc.thenCompose { allPreviousComplete ->
+ if (allPreviousComplete) {
+ newFuture.thenApply { allPreviousComplete && it }
+ } else {
+ SafeFuture.completedFuture(false)
+ }
+ }
+ }
+ }
+ }
+
+ private fun sendMessages(): List {
+ val baseMessageToSend =
+ L1EventQuerierIntegrationTest.L1MessageToSend(
+ l2RecipientAddress,
+ BigInteger.TEN,
+ ByteArray(0),
+ BigInteger.valueOf(100001)
+ )
+ val messagesToSend = listOf(
+ baseMessageToSend,
+ baseMessageToSend.copy(fee = BigInteger.valueOf(11)),
+ baseMessageToSend.copy(value = BigInteger.valueOf(100001)),
+ baseMessageToSend.copy(value = BigInteger.valueOf(100001)),
+ baseMessageToSend.copy(value = BigInteger.valueOf(100001))
+ )
+
+ val emittedEvents = messagesToSend.map {
+ contract.sendMessage(it.recipient, it.fee, it.calldata, it.value).send()
+ }.map {
+ log.debug("Message has been sent in block {}", it.blockNumber)
+ ZkEvmV2.staticExtractEventParameters(
+ ZkEvmV2.MESSAGESENT_EVENT,
+ it.logs.first { log ->
+ log.topics.contains(EventEncoder.encode(ZkEvmV2.MESSAGESENT_EVENT))
+ }
+ )
+ }.map { Bytes32.wrap(it.indexedValues[2].value as ByteArray) }
+
+ return emittedEvents
+ }
+}
diff --git a/coordinator/app/src/integrationTest/resources/log4j2.xml b/coordinator/app/src/integrationTest/resources/log4j2.xml
new file mode 100644
index 000000000..79e80577c
--- /dev/null
+++ b/coordinator/app/src/integrationTest/resources/log4j2.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/coordinator/app/src/integrationTest/resources/vertx-options.json b/coordinator/app/src/integrationTest/resources/vertx-options.json
new file mode 100644
index 000000000..80e459566
--- /dev/null
+++ b/coordinator/app/src/integrationTest/resources/vertx-options.json
@@ -0,0 +1,8 @@
+{
+ "blockedThreadCheckInterval" : 5,
+ "blockedThreadCheckIntervalUnit" : "MINUTES",
+ "maxEventLoopExecuteTime" : 2,
+ "maxEventLoopExecuteTimeUnit" : "MINUTES",
+ "maxWorkerExecuteTime": 5,
+ "maxWorkerExecuteTimeUnit": "MINUTES"
+}
diff --git a/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/api/Api.kt b/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/api/Api.kt
new file mode 100644
index 000000000..26b685f92
--- /dev/null
+++ b/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/api/Api.kt
@@ -0,0 +1,26 @@
+package net.consensys.zkevm.coordinator.api
+
+import io.vertx.core.Future
+import io.vertx.core.Vertx
+import net.consensys.linea.vertx.ObservabilityServer
+
+class Api(
+ private val configs: Config,
+ private val vertx: Vertx
+) {
+ data class Config(
+ val observabilityPort: UInt
+ )
+
+ private var observabilityServerId: String? = null
+
+ fun start(): Future<*> {
+ val observabilityServer =
+ ObservabilityServer(ObservabilityServer.Config("coordinator", configs.observabilityPort.toInt()))
+ return vertx.deployVerticle(observabilityServer)
+ }
+
+ fun stop(): Future<*> {
+ return this.observabilityServerId?.let { vertx.undeploy(it) } ?: Future.succeededFuture(null)
+ }
+}
diff --git a/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/app/BlockchainClientHelper.kt b/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/app/BlockchainClientHelper.kt
new file mode 100644
index 000000000..22d427c0f
--- /dev/null
+++ b/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/app/BlockchainClientHelper.kt
@@ -0,0 +1,73 @@
+package net.consensys.zkevm.coordinator.app
+
+import io.vertx.core.Vertx
+import io.vertx.core.http.HttpVersion
+import io.vertx.ext.web.client.WebClientOptions
+import net.consensys.linea.contract.AsyncFriendlyTransactionManager
+import net.consensys.linea.contract.ZkEvmV2AsyncFriendly
+import net.consensys.linea.httprest.client.VertxHttpRestClient
+import net.consensys.zkevm.ethereum.coordination.dynamicgasprice.FeesCalculator
+import net.consensys.zkevm.ethereum.coordination.dynamicgasprice.FeesFetcher
+import net.consensys.zkevm.ethereum.coordination.dynamicgasprice.WMAGasProvider
+import net.consensys.zkevm.ethereum.crypto.ECKeypairSignerAdapter
+import net.consensys.zkevm.ethereum.crypto.Web3SignerRestClient
+import net.consensys.zkevm.ethereum.crypto.Web3SignerTxSignService
+import org.web3j.crypto.Credentials
+import org.web3j.protocol.Web3j
+import org.web3j.service.TxSignServiceImpl
+import org.web3j.utils.Numeric
+import java.net.URI
+
+fun createTransactionManager(
+ vertx: Vertx,
+ signerConfig: SignerConfig,
+ client: Web3j
+): AsyncFriendlyTransactionManager {
+ val transactionSignService = when (signerConfig.type) {
+ SignerConfig.Type.Web3j -> {
+ TxSignServiceImpl(Credentials.create(signerConfig.web3j!!.privateKey.value))
+ }
+
+ SignerConfig.Type.Web3Signer -> {
+ val web3SignerConfig = signerConfig.web3signer!!
+ val endpoint = URI(web3SignerConfig.endpoint)
+ val webClientOptions: WebClientOptions =
+ WebClientOptions()
+ .setKeepAlive(web3SignerConfig.keepAlive)
+ .setProtocolVersion(HttpVersion.HTTP_1_1)
+ .setMaxPoolSize(web3SignerConfig.maxPoolSize.toInt())
+ .setDefaultHost(endpoint.host)
+ .setDefaultPort(endpoint.port)
+ val httpRestClient = VertxHttpRestClient(webClientOptions, vertx)
+ val signer = Web3SignerRestClient(httpRestClient, signerConfig.web3signer.publicKey)
+ val signerAdapter = ECKeypairSignerAdapter(signer, Numeric.toBigInt(signerConfig.web3signer.publicKey))
+ val web3SignerCredentials = Credentials.create(signerAdapter)
+ Web3SignerTxSignService(web3SignerCredentials)
+ }
+ }
+
+ return AsyncFriendlyTransactionManager(client, transactionSignService, client.ethChainId().send().id)
+}
+
+fun instantiateZkEvmContractClient(
+ l1Config: L1Config,
+ transactionManager: AsyncFriendlyTransactionManager,
+ gasFetcher: FeesFetcher,
+ wmaFeesCalculator: FeesCalculator,
+ client: Web3j
+): ZkEvmV2AsyncFriendly {
+ return ZkEvmV2AsyncFriendly.load(
+ l1Config.zkEvmContractAddress,
+ client,
+ transactionManager,
+ WMAGasProvider(
+ client.ethChainId().send().chainId.toLong(),
+ gasFetcher,
+ wmaFeesCalculator,
+ WMAGasProvider.Config(
+ l1Config.gasLimit,
+ l1Config.maxFeePerGas
+ )
+ )
+ )
+}
diff --git a/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/app/CoordinatorApp.kt b/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/app/CoordinatorApp.kt
new file mode 100644
index 000000000..d476bb2b4
--- /dev/null
+++ b/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/app/CoordinatorApp.kt
@@ -0,0 +1,311 @@
+package net.consensys.zkevm.coordinator.app
+
+import com.fasterxml.jackson.databind.module.SimpleModule
+import io.micrometer.core.instrument.MeterRegistry
+import io.vertx.core.Vertx
+import io.vertx.core.VertxOptions
+import io.vertx.core.json.jackson.DatabindCodec
+import io.vertx.micrometer.MicrometerMetricsOptions
+import io.vertx.micrometer.VertxPrometheusOptions
+import io.vertx.micrometer.backends.BackendRegistries
+import io.vertx.sqlclient.SqlClient
+import net.consensys.linea.async.toSafeFuture
+import net.consensys.linea.jsonrpc.client.LoadBalancingJsonRpcClient
+import net.consensys.linea.jsonrpc.client.VertxHttpJsonRpcClientFactory
+import net.consensys.linea.vertx.loadVertxConfig
+import net.consensys.zkevm.coordinator.api.Api
+import net.consensys.zkevm.coordinator.blockcreation.BlockCreationMonitor
+import net.consensys.zkevm.coordinator.blockcreation.ExtendedWeb3JImpl
+import net.consensys.zkevm.coordinator.blockcreation.GethCliqueSafeBlockProvider
+import net.consensys.zkevm.coordinator.blockcreation.TracesFilesManager
+import net.consensys.zkevm.coordinator.clients.FileBasedProverClient
+import net.consensys.zkevm.coordinator.clients.TracesGeneratorJsonRpcClientV1
+import net.consensys.zkevm.coordinator.clients.Type2StateManagerClient
+import net.consensys.zkevm.coordinator.clients.Type2StateManagerJsonRpcClient
+import net.consensys.zkevm.ethereum.coordination.blockcreation.BlockCreated
+import net.consensys.zkevm.ethereum.coordination.conflation.Batch
+import net.consensys.zkevm.ethereum.coordination.conflation.BlockToBatchSubmissionCoordinator
+import net.consensys.zkevm.ethereum.coordination.conflation.BlocksTracesConflationCalculatorImpl
+import net.consensys.zkevm.ethereum.coordination.conflation.ConflationCalculatorConfig
+import net.consensys.zkevm.ethereum.coordination.conflation.ConflationService
+import net.consensys.zkevm.ethereum.coordination.conflation.ConflationServiceImpl
+import net.consensys.zkevm.ethereum.coordination.conflation.DataLimits
+import net.consensys.zkevm.ethereum.coordination.conflation.TracesConflationCoordinatorImpl
+import net.consensys.zkevm.ethereum.coordination.proofcreation.FileBasedZkProofCreationCoordinator
+import net.consensys.zkevm.ethereum.finalization.FinalizationMonitor
+import net.consensys.zkevm.ethereum.settlement.persistence.Db
+import net.consensys.zkevm.ethereum.settlement.persistence.PostgresBatchesRepository
+import org.apache.logging.log4j.LogManager
+import org.apache.logging.log4j.Logger
+import org.apache.tuweni.bytes.Bytes
+import org.apache.tuweni.bytes.Bytes32
+import org.web3j.protocol.Web3j
+import org.web3j.protocol.http.HttpService
+import org.web3j.utils.Async
+import tech.pegasys.teku.ethereum.executionclient.serialization.BytesSerializer
+import tech.pegasys.teku.infrastructure.async.SafeFuture
+import java.net.URL
+import kotlin.time.toKotlinDuration
+
+class CoordinatorApp(private val configs: CoordinatorConfig) {
+ private val log: Logger = LogManager.getLogger(this::class.java)
+ private val vertx: Vertx = run {
+ log.trace("System properties: {}", System.getProperties())
+ val vertxConfigJson = loadVertxConfig(System.getProperty("vertx.configurationFile"))
+ log.info("Vertx custom configs: {}", vertxConfigJson)
+ val vertxConfig =
+ VertxOptions(vertxConfigJson)
+ .setMetricsOptions(
+ MicrometerMetricsOptions()
+ .setJvmMetricsEnabled(true)
+ .setPrometheusOptions(
+ VertxPrometheusOptions().setPublishQuantiles(true).setEnabled(true)
+ )
+ .setEnabled(true)
+ )
+ log.debug("Vertx full configs: {}", vertxConfig)
+ log.info("App configs: {}", configs)
+
+ // TODO: adapt JsonMessageProcessor to use custom ObjectMapper
+ // this is just dark magic.
+ val module = SimpleModule()
+ module.addSerializer(Bytes::class.java, BytesSerializer())
+ DatabindCodec.mapper().registerModule(module)
+ // .enable(SerializationFeature.INDENT_OUTPUT)
+ Vertx.vertx(vertxConfig)
+ }
+ private val meterRegistry: MeterRegistry = BackendRegistries.getDefaultNow()
+ private val httpJsonRpcClientFactory = VertxHttpJsonRpcClientFactory(vertx, meterRegistry)
+ private val api = Api(
+ Api.Config(
+ configs.api.observabilityPort
+ ),
+ vertx
+ )
+ private val l2Web3jClient: Web3j =
+ Web3j.build(
+ HttpService(configs.sequencer.ethApi.toString()),
+ 1000,
+ Async.defaultExecutorService()
+ )
+ private val l2ZkGethWeb3jClient: Web3j =
+ Web3j.build(
+ HttpService(configs.zkGethTraces.ethApi.toString()),
+ 1000,
+ Async.defaultExecutorService()
+ )
+ private val proverClient: FileBasedProverClient =
+ FileBasedProverClient(
+ config =
+ FileBasedProverClient.Config(
+ requestDirectory = configs.prover.fsInputDirectory,
+ responseDirectory = configs.prover.fsOutputDirectory,
+ inprogessProvingSuffixPattern = configs.prover.fsInprogessProvingSuffixPattern,
+ pollingInterval = configs.prover.fsPollingInterval.toKotlinDuration(),
+ timeout = configs.prover.timeout.toKotlinDuration(),
+ proverVersion = configs.prover.version,
+ l2MessageServiceAddress = configs.l2.messageServiceAddress,
+ tracesVersion = configs.traces.version,
+ stateManagerVersion = configs.stateManager.version
+ ),
+ vertx = vertx,
+ l2Web3jClient = l2Web3jClient
+ )
+
+ private val sqlClient: SqlClient = initDb(configs.database)
+ private val batchesRepository =
+ PostgresBatchesRepository(
+ PostgresBatchesRepository.Config(configs.batchSubmission.maxBatchesToSendPerTick.toUInt()),
+ sqlClient,
+ proverClient.proverResponsesRepository
+ )
+ private val l1App = L1DependentApp(
+ configs,
+ vertx,
+ l2Web3jClient,
+ httpJsonRpcClientFactory,
+ batchesRepository
+ ) { update: FinalizationMonitor.FinalizationUpdate ->
+ batchesRepository.setBatchStatusUpToEndBlockNumber(
+ update.blockNumber,
+ Batch.Status.Pending,
+ Batch.Status.Finalized
+ )
+ }
+
+ private val lastFinalizedBlockNumber: ULong = run {
+ when {
+ configs.conflation.forceStartingBlock != null -> {
+ configs.conflation.forceStartingBlock.toULong() - 1UL
+ }
+
+ !configs.testL1Disabled -> l1App.lastFinalizedBlock().get()
+ // Beginning of Rollup
+ else -> 0UL
+ }.also {
+ log.info("Last finalized block: {}", it)
+ }
+ }
+
+ private val extendedWeb3j = ExtendedWeb3JImpl(l2ZkGethWeb3jClient)
+ private val blockCreationMonitor = run {
+ log.info("Resuming conflation from block {}", lastFinalizedBlockNumber + 1UL)
+ val parentBlock = extendedWeb3j.ethGetExecutionPayloadByNumber(lastFinalizedBlockNumber.toLong()).get()
+ BlockCreationMonitor(
+ vertx,
+ extendedWeb3j,
+ parentBlock.blockNumber.longValue() + 1,
+ parentBlock.blockHash,
+ { blockEvent: BlockCreated -> block2BatchCoordinator.acceptBlock(blockEvent) },
+ BlockCreationMonitor.Config(
+ configs.zkGethTraces.newBlockPollingInterval.toKotlinDuration(),
+ configs.l2.blocksToFinalization.toLong()
+ )
+ )
+ }
+
+ private val conflationService: ConflationService = ConflationServiceImpl(
+ BlocksTracesConflationCalculatorImpl(
+ lastFinalizedBlockNumber,
+ GethCliqueSafeBlockProvider(
+ extendedWeb3j,
+ GethCliqueSafeBlockProvider.Config(configs.l2.blocksToFinalization.toLong())
+ ),
+ ConflationCalculatorConfig(
+ tracesConflationLimit = configs.conflation.tracesLimits,
+ dataConflationLimits = DataLimits(
+ totalLimitBytes = configs.conflation.totalLimitBytes.toUInt(),
+ perBlockOverheadBytes = configs.conflation.perBlockOverheadBytes.toUInt(),
+ minBlockL1SizeBytes = configs.conflation.minBlockL1SizeBytes.toUInt()
+ ),
+ conflationDeadline = configs.conflation.conflationDeadline.toKotlinDuration(),
+ conflationDeadlineCheckInterval = configs.conflation.conflationDeadlineCheckInterval.toKotlinDuration(),
+ conflationDeadlineLastBlockConfirmationDelay =
+ configs.conflation.conflationDeadlineLastBlockConfirmationDelay.toKotlinDuration(),
+ blocksLimit = configs.conflation.blocksLimit?.toUInt()
+ )
+ )
+ )
+
+ private val block2BatchCoordinator = run {
+ val tracesFileManager =
+ TracesFilesManager(
+ vertx,
+ TracesFilesManager.Config(
+ configs.traces.fileManager.rawTracesDirectory,
+ configs.traces.fileManager.nonCanonicalRawTracesDirectory,
+ configs.traces.fileManager.pollingInterval.toKotlinDuration(),
+ configs.traces.fileManager.tracesFileCreationWaitTimeout.toKotlinDuration(),
+ configs.traces.version,
+ configs.traces.fileManager.tracesFileExtension,
+ configs.traces.fileManager.createNonCanonicalDirectory
+ )
+ )
+ val zkStateClient: Type2StateManagerClient = Type2StateManagerJsonRpcClient(
+ LoadBalancingJsonRpcClient(
+ configs.stateManager.endpoints.map { httpJsonRpcClientFactory.create(it) },
+ configs.stateManager.requestLimitPerEndpoint
+ ),
+ Type2StateManagerJsonRpcClient.Config(configs.stateManager.version)
+ )
+
+ val tracesCountersClient = TracesGeneratorJsonRpcClientV1(
+ vertx,
+ createLoadBalancerRpcClient(
+ httpJsonRpcClientFactory,
+ configs.traces.counters.endpoints,
+ configs.traces.counters.requestLimitPerEndpoint
+ ),
+ TracesGeneratorJsonRpcClientV1.Config(
+ configs.traces.counters.requestMaxRetries.toInt(),
+ configs.traces.counters.requestRetryInterval.toKotlinDuration()
+ )
+ )
+ val tracesConflationClient = TracesGeneratorJsonRpcClientV1(
+ vertx,
+ createLoadBalancerRpcClient(
+ httpJsonRpcClientFactory,
+ configs.traces.conflation.endpoints,
+ configs.traces.conflation.requestLimitPerEndpoint
+ ),
+ TracesGeneratorJsonRpcClientV1.Config(
+ configs.traces.conflation.requestMaxRetries.toInt(),
+ configs.traces.conflation.requestRetryInterval.toKotlinDuration()
+ )
+ )
+ BlockToBatchSubmissionCoordinator(
+ conflationService,
+ tracesFileManager,
+ tracesCountersClient,
+ TracesConflationCoordinatorImpl(tracesConflationClient, zkStateClient),
+ FileBasedZkProofCreationCoordinator(proverClient),
+ l1App.batchSubmissionCoordinator,
+ vertx
+ )
+ }
+
+ init {
+ log.info("Coordinator app instantiated")
+ }
+
+ fun start() {
+ SafeFuture.allOf(
+ l1App.start(),
+ blockCreationMonitor.start()
+ ).thenCompose {
+ api.start().toSafeFuture()
+ }.get()
+
+ log.info("Started :)")
+ }
+
+ fun stop(): Int {
+ SafeFuture.allOf(
+ l1App.stop(),
+ blockCreationMonitor.stop(),
+ SafeFuture.fromRunnable { l2Web3jClient.shutdown() },
+ api.stop().toSafeFuture()
+ ).thenCompose {
+ vertx.close().toSafeFuture()
+ }
+ .get()
+ return 0
+ }
+
+ data class BlockNumberAndHash(
+ val blockNumber: ULong,
+ val blockHash: Bytes32,
+ val parentHash: Bytes32
+ )
+
+ private fun initDb(dbConfig: DatabaseConfig): SqlClient {
+ Db.applyDbMigrations(
+ dbConfig.host,
+ dbConfig.port,
+ dbConfig.schema,
+ dbConfig.username,
+ dbConfig.password.value
+ )
+ return Db.vertxSqlClient(
+ vertx,
+ dbConfig.host,
+ dbConfig.port,
+ dbConfig.schema,
+ dbConfig.username,
+ dbConfig.password.value,
+ dbConfig.transactionalPoolSize,
+ dbConfig.readPipeliningLimit
+ )
+ }
+
+ private fun createLoadBalancerRpcClient(
+ httpJsonRpcClientFactory: VertxHttpJsonRpcClientFactory,
+ endpoints: List,
+ requestLimitPerEndpoint: UInt
+ ): LoadBalancingJsonRpcClient {
+ return LoadBalancingJsonRpcClient(
+ endpoints.map(httpJsonRpcClientFactory::create),
+ requestLimitPerEndpoint
+ )
+ }
+}
diff --git a/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/app/CoordinatorAppCli.kt b/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/app/CoordinatorAppCli.kt
new file mode 100644
index 000000000..c0c188cdb
--- /dev/null
+++ b/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/app/CoordinatorAppCli.kt
@@ -0,0 +1,135 @@
+package net.consensys.zkevm.coordinator.app
+
+import com.sksamuel.hoplite.ConfigLoaderBuilder
+import com.sksamuel.hoplite.addFileSource
+import org.apache.logging.log4j.LogManager
+import org.apache.logging.log4j.Logger
+import picocli.CommandLine
+import picocli.CommandLine.Command
+import picocli.CommandLine.Parameters
+import java.io.File
+import java.io.PrintWriter
+import java.nio.charset.Charset
+import java.util.concurrent.Callable
+
+@Command(
+ name = CoordinatorAppCli.COMMAND_NAME,
+ showDefaultValues = true,
+ abbreviateSynopsis = true,
+ description = ["Runs Linea Coordinator"],
+ version = ["0.0.1"],
+ synopsisHeading = "%n",
+ descriptionHeading = "%nDescription:%n%n",
+ optionListHeading = "%nOptions:%n",
+ footerHeading = "%n"
+)
+class CoordinatorAppCli
+internal constructor(private val errorWriter: PrintWriter, private val startAction: StartAction) :
+ Callable {
+ @Parameters(paramLabel = "CONFIG.toml", description = ["Configuration files"])
+ private val configFiles: List? = null
+
+ @CommandLine.Option(
+ names = ["--traces-limits"],
+ paramLabel = "",
+ description = ["Prover traces limits"],
+ arity = "1"
+ )
+ private val tracesLimitsFile: File? = null
+
+ override fun call(): Int {
+ return try {
+ if (configFiles == null) {
+ errorWriter.println("Please provide a configuration file!")
+ printUsage(errorWriter)
+ return 1
+ }
+ if (tracesLimitsFile == null) {
+ errorWriter.println("Please provide traces-limits file!")
+ printUsage(errorWriter)
+ return 1
+ }
+ for (configFile in configFiles) {
+ if (!canReadFile(configFile)) {
+ return 1
+ }
+ }
+ val tracesLimitsConfigs =
+ loadConfigs(listOf(tracesLimitsFile), errorWriter) ?: return 1
+ val configs =
+ loadConfigs(configFiles, errorWriter)
+ ?.let { config: CoordinatorConfig ->
+ config.copy(
+ conflation = config.conflation.copy(_tracesLimits = tracesLimitsConfigs.tracesLimits)
+ )
+ }
+ ?: return 1
+
+ startAction.start(configs)
+ 0
+ } catch (e: Exception) {
+ reportUserError(e)
+ 1
+ }
+ }
+
+ private fun canReadFile(file: File): Boolean {
+ if (!file.canRead()) {
+ errorWriter.println("Cannot read configuration file '${file.absolutePath}'")
+ return false
+ }
+ return true
+ }
+
+ fun reportUserError(ex: Throwable) {
+ logger.fatal(ex.message, ex)
+ errorWriter.println(ex.message)
+ printUsage(errorWriter)
+ }
+
+ private fun printUsage(outputWriter: PrintWriter) {
+ outputWriter.println()
+ outputWriter.println("To display full help:")
+ outputWriter.println(COMMAND_NAME + " --help")
+ }
+
+ /**
+ * Not using a static field for this log instance because some code in this class executes prior
+ * to the logging configuration being applied so it's not always safe to use the logger.
+ *
+ * Where this is used we also ensure the messages are printed to the error writer so they will be
+ * printed even if logging is not yet configured.
+ *
+ * @return the logger for this class
+ */
+ private val logger: Logger = LogManager.getLogger()
+
+ fun interface StartAction {
+ fun start(configs: CoordinatorConfig)
+ }
+
+ companion object {
+ const val COMMAND_NAME = "coordinator"
+ fun withAction(startAction: StartAction): CoordinatorAppCli {
+ val errorWriter = PrintWriter(System.err, true, Charset.defaultCharset())
+ return CoordinatorAppCli(errorWriter, startAction)
+ }
+
+ inline fun loadConfigs(configFiles: List, errorWriter: PrintWriter): T? {
+ val confBuilder: ConfigLoaderBuilder = ConfigLoaderBuilder.Companion.empty().addDefaults()
+ for (i in configFiles.indices.reversed()) {
+ // files must be added in reverse order for overriding
+ confBuilder.addFileSource(configFiles[i], false)
+ }
+
+ return confBuilder.build().loadConfig(emptyList()).let { config ->
+ if (config.isInvalid()) {
+ errorWriter.println(config.getInvalidUnsafe().description())
+ null
+ } else {
+ config.getUnsafe()
+ }
+ }
+ }
+ }
+}
diff --git a/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/app/CoordinatorAppMain.kt b/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/app/CoordinatorAppMain.kt
new file mode 100644
index 000000000..fa08741d3
--- /dev/null
+++ b/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/app/CoordinatorAppMain.kt
@@ -0,0 +1,46 @@
+package net.consensys.zkevm.coordinator.app
+
+import org.apache.logging.log4j.LogManager
+import org.apache.logging.log4j.core.LoggerContext
+import org.apache.logging.log4j.core.config.Configurator
+import picocli.CommandLine
+import kotlin.system.exitProcess
+
+class CoordinatorAppMain {
+ companion object {
+ private val log = LogManager.getLogger(CoordinatorAppMain::class)
+
+ @JvmStatic
+ fun main(args: Array) {
+ val cmd = CommandLine(CoordinatorAppCli.withAction(::startApp))
+ cmd.setExecutionExceptionHandler { ex, _, _ ->
+ log.error("Execution failure: ", ex)
+ 1
+ }
+ cmd.setParameterExceptionHandler { ex, _ ->
+ log.error("Invalid args!: ", ex)
+ 1
+ }
+ val exitCode = cmd.execute(*args)
+ if (exitCode != 0) {
+ exitProcess(exitCode)
+ }
+ }
+
+ private fun startApp(configs: CoordinatorConfig) {
+ val app = CoordinatorApp(configs)
+ Runtime.getRuntime()
+ .addShutdownHook(
+ Thread {
+ app.stop()
+ if (LogManager.getContext() is LoggerContext) {
+ // Disable log4j auto shutdown hook is not used otherwise
+ // Messages in App.stop won't appear in the logs
+ Configurator.shutdown(LogManager.getContext() as LoggerContext)
+ }
+ }
+ )
+ app.start()
+ }
+ }
+}
diff --git a/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/app/CoordinatorConfig.kt b/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/app/CoordinatorConfig.kt
new file mode 100644
index 000000000..5c1857c88
--- /dev/null
+++ b/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/app/CoordinatorConfig.kt
@@ -0,0 +1,273 @@
+package net.consensys.zkevm.coordinator.app
+
+import com.sksamuel.hoplite.ConfigAlias
+import com.sksamuel.hoplite.Masked
+import net.consensys.linea.traces.TracesCounters
+import net.consensys.linea.traces.TracingModule
+import tech.pegasys.teku.infrastructure.bytes.Bytes20
+import java.math.BigDecimal
+import java.math.BigInteger
+import java.net.URL
+import java.nio.file.Path
+import java.time.Duration
+
+data class ApiConfig(
+ val observabilityPort: UInt
+// @ConfigAlias("fork-choice-provider") val forkChoiceProvider: ForkChoiceStateApiConfig
+)
+
+data class ConflationConfig(
+ val consistentNumberOfBlocksOnL1ToWait: Int,
+ val conflationDeadline: Duration,
+ val conflationDeadlineCheckInterval: Duration,
+ val conflationDeadlineLastBlockConfirmationDelay: Duration,
+ val totalLimitBytes: Int,
+ val perBlockOverheadBytes: Int,
+ val minBlockL1SizeBytes: Int,
+ val blocksLimit: Long? = null,
+ val forceStartingBlock: Long? = null,
+ private var _tracesLimits: TracesCounters?
+) {
+
+ init {
+ require(minBlockL1SizeBytes > 0) { "minBlockL1SizeBytes must be greater than 0" }
+ require(perBlockOverheadBytes > 0) { "perBlockOverheadBytes must be greater than 0" }
+ require(totalLimitBytes > minBlockL1SizeBytes) {
+ "totalLimitBytes must be greater than minBlockL1SizeBytes: " +
+ "totalLimitBytes=$totalLimitBytes, minBlockL1SizeBytes=$minBlockL1SizeBytes"
+ }
+ require(conflationDeadlineCheckInterval <= conflationDeadline) {
+ "Clock ticker interval must be smaller than conflation deadline"
+ }
+ consistentNumberOfBlocksOnL1ToWait.let {
+ require(it > 0) { "consistentNumberOfBlocksOnL1ToWait must be grater than 0" }
+ }
+ forceStartingBlock?.let { require(it > 0) { "forceStartingBlock must be grater than 0" } }
+ blocksLimit?.let { require(it > 0) { "blocksLimit must be grater than 0" } }
+ }
+
+ val tracesLimits: TracesCounters
+ get() = run {
+ _tracesLimits?.also { limits ->
+ if (limits.keys != TracingModule.allModules) {
+ val missingModules = TracingModule.allModules - limits.keys
+ val extraModules = limits.keys - TracingModule.allModules
+ val errorMessage =
+ "Invalid traces limits: Missing modules: " +
+ "${missingModules.joinToString(",", "[", "]")}, " +
+ "unsupported modules=${extraModules.joinToString(",", "[", "]")}"
+ throw IllegalStateException(errorMessage)
+ }
+ } ?: throw IllegalStateException("Traces limits not defined!")
+ }
+}
+
+data class ZkGethTraces(
+ val ethApi: URL,
+ val newBlockPollingInterval: Duration
+)
+
+data class SequencerConfig(
+ val ethApi: URL
+ // val version: String,
+ // val engineApi: URL,
+ // val suggestedFeeRecipient: String,
+ // val blockInterval: Duration,
+ // @ConfigAlias("jwtsecret-file") val jwtSecretFile: Path
+)
+
+data class ProverConfig(
+ val version: String,
+ val fsInputDirectory: Path,
+ val fsOutputDirectory: Path,
+ val fsPollingInterval: Duration,
+ val fsInprogessProvingSuffixPattern: String,
+ val timeout: Duration
+)
+
+data class TracesConfig(
+ val version: String,
+ val counters: FunctionalityEndpoint,
+ val conflation: FunctionalityEndpoint,
+ val fileManager: FileManager
+) {
+ data class FunctionalityEndpoint(
+ val endpoints: List,
+ val requestLimitPerEndpoint: UInt,
+ val requestMaxRetries: UInt,
+ val requestRetryInterval: Duration
+ ) {
+ init {
+ require(requestLimitPerEndpoint > 0u) { "requestLimitPerEndpoint must be greater than 0" }
+ }
+ }
+
+ data class FileManager(
+ val tracesFileExtension: String,
+ val rawTracesDirectory: Path,
+ val nonCanonicalRawTracesDirectory: Path,
+ val createNonCanonicalDirectory: Boolean,
+ val pollingInterval: Duration,
+ val tracesFileCreationWaitTimeout: Duration
+ )
+}
+
+data class StateManagerClientConfig(
+ val version: String,
+ val endpoints: List,
+ val requestLimitPerEndpoint: UInt
+) {
+ init {
+ require(requestLimitPerEndpoint > 0u) { "requestLimitPerEndpoint must be greater than 0" }
+ }
+}
+
+data class BatchSubmissionConfig(
+ val maxBatchesToSendPerTick: Int,
+ val proofSubmissionDelay: Duration,
+ override var disabled: Boolean = false
+) : FeatureToggleable {
+ init {
+ require(maxBatchesToSendPerTick > 0) { "maxBatchesToSendPerTick must be greater than 0" }
+ }
+}
+
+data class DatabaseConfig(
+ val host: String,
+ val port: Int,
+ val username: String,
+ val password: Masked,
+ val schema: String,
+ val readPoolSize: Int,
+ val readPipeliningLimit: Int,
+ val transactionalPoolSize: Int
+)
+
+data class L1Config(
+ val zkEvmContractAddress: String,
+ val rpcEndpoint: URL,
+ val finalizationPollingInterval: Duration,
+ val newBatchPollingInterval: Duration,
+ val blocksToFinalization: UInt,
+ val gasLimit: BigInteger,
+ val feeHistoryBlockCount: Int,
+ val feeHistoryRewardPercentile: Double,
+ val maxFeePerGas: BigInteger,
+ val earliestBlock: BigInteger,
+ val sendMessageEventPollingInterval: Duration,
+ val maxEventScrapingTime: Duration,
+ val maxMessagesToCollect: UInt,
+ val finalizedBlockTag: String,
+ val blockRangeLoopLimit: UInt
+)
+
+data class L2Config(
+ @ConfigAlias("message-service-address") private val _messageServiceAddress: String,
+ val gasLimit: BigInteger,
+ val maxFeePerGasCap: BigInteger,
+ val feeHistoryBlockCount: UInt,
+ val feeHistoryRewardPercentile: Double,
+ val blocksToFinalization: UInt,
+ val lastHashSearchWindow: UInt,
+ val lastHashSearchMaxBlocksBack: UInt,
+ val anchoringReceiptPollingInterval: Duration,
+ val maxReceiptRetries: UInt
+) {
+ // thi guarantees that no invalid address is set in the application
+ val messageServiceAddress: Bytes20 = Bytes20.fromHexString(_messageServiceAddress)
+}
+
+data class SignerConfig(
+ val type: Type,
+ val web3signer: Web3SignerConfig?,
+ val web3j: Web3jConfig?
+) {
+ enum class Type {
+ Web3j,
+ Web3Signer
+ }
+
+ init {
+ when (type) {
+ Type.Web3Signer -> {
+ if (web3signer == null) throw IllegalStateException("Signer $type configuration is null.")
+ }
+
+ Type.Web3j -> {
+ if (web3j == null) throw IllegalStateException("Signer $type configuration is null.")
+ }
+ }
+ }
+}
+
+data class Web3jConfig(
+ val privateKey: Masked
+)
+
+data class Web3SignerConfig(
+ val endpoint: String,
+ val maxPoolSize: UInt,
+ val keepAlive: Boolean,
+ val publicKey: String
+)
+
+interface FeatureToggleable {
+ val disabled: Boolean
+ val enabled: Boolean
+ get() = !disabled
+}
+
+data class MessageAnchoringServiceConfig(
+ val pollingInterval: Duration,
+ val maxMessagesToAnchor: UInt,
+ override var disabled: Boolean = false
+) : FeatureToggleable {
+ init {
+ require(maxMessagesToAnchor > 0u) { "maxMessagesToAnchor must be greater than 0" }
+ }
+}
+
+data class DynamicGasPriceServiceConfig(
+ val pollingInterval: Duration,
+ val feeHistoryBlockCount: Int,
+ val feeHistoryRewardPercentile: Double,
+ val baseFeeCoefficient: BigDecimal,
+ val priorityFeeCoefficient: BigDecimal,
+ val gasPriceCap: BigInteger,
+ val minerGasPriceUpdateRecipients: List,
+ override var disabled: Boolean = false
+) : FeatureToggleable {
+
+ init {
+ require(feeHistoryBlockCount > 0) { "feeHistoryBlockCount must be greater than 0" }
+ }
+}
+
+data class TracesLimitsConfigFile(val tracesLimits: TracesCounters)
+
+data class CoordinatorConfig(
+ val sequencer: SequencerConfig,
+ val zkGethTraces: ZkGethTraces,
+ val prover: ProverConfig,
+ val traces: TracesConfig,
+ val l1: L1Config,
+ val l2: L2Config,
+ val l1Signer: SignerConfig,
+ val batchSubmission: BatchSubmissionConfig,
+ val database: DatabaseConfig,
+ val stateManager: StateManagerClientConfig,
+ val conflation: ConflationConfig,
+ val api: ApiConfig,
+ val l2Signer: SignerConfig,
+ val messageAnchoringService: MessageAnchoringServiceConfig,
+ val dynamicGasPriceService: DynamicGasPriceServiceConfig,
+ val testL1Disabled: Boolean = false
+) {
+ init {
+ if (testL1Disabled) {
+ messageAnchoringService.disabled = true
+ batchSubmission.disabled = true
+ dynamicGasPriceService.disabled = true
+ }
+ }
+}
diff --git a/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/app/GasPriceUpdaterApp.kt b/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/app/GasPriceUpdaterApp.kt
new file mode 100644
index 000000000..f75fdcaef
--- /dev/null
+++ b/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/app/GasPriceUpdaterApp.kt
@@ -0,0 +1,77 @@
+package net.consensys.zkevm.coordinator.app
+
+import io.vertx.core.Vertx
+import net.consensys.linea.jsonrpc.client.VertxHttpJsonRpcClientFactory
+import net.consensys.zkevm.LongRunningService
+import net.consensys.zkevm.ethereum.coordination.dynamicgasprice.DynamicGasPriceService
+import net.consensys.zkevm.ethereum.coordination.dynamicgasprice.FeeHistoryFetcherImpl
+import net.consensys.zkevm.ethereum.coordination.dynamicgasprice.FeesCalculator
+import net.consensys.zkevm.ethereum.coordination.dynamicgasprice.FeesFetcher
+import net.consensys.zkevm.ethereum.coordination.dynamicgasprice.GasPriceUpdater
+import net.consensys.zkevm.ethereum.coordination.dynamicgasprice.GasPriceUpdaterImpl
+import net.consensys.zkevm.ethereum.coordination.dynamicgasprice.GasUsageRatioWeightedAverageFeesCalculator
+import org.apache.logging.log4j.LogManager
+import org.web3j.protocol.Web3j
+import java.util.concurrent.CompletableFuture
+import kotlin.time.toKotlinDuration
+
+class GasPriceUpdaterApp(
+ vertx: Vertx,
+ httpJsonRpcClientFactory: VertxHttpJsonRpcClientFactory,
+ l1Web3jClient: Web3j,
+ configs: Config
+) : LongRunningService {
+ private val log = LogManager.getLogger(this::class.java)
+
+ data class Config(
+ val dynamicGasPriceService: DynamicGasPriceServiceConfig
+ )
+
+ private val dynamicGasPriceService: DynamicGasPriceService = run {
+ val gasServiceFeesFetcher: FeesFetcher = FeeHistoryFetcherImpl(
+ l1Web3jClient,
+ FeeHistoryFetcherImpl.Config(
+ configs.dynamicGasPriceService.feeHistoryBlockCount.toUInt(),
+ configs.dynamicGasPriceService.feeHistoryRewardPercentile
+ )
+ )
+
+ val l2MinMinerTipCalculator: FeesCalculator = GasUsageRatioWeightedAverageFeesCalculator(
+ GasUsageRatioWeightedAverageFeesCalculator.Config(
+ configs.dynamicGasPriceService.baseFeeCoefficient,
+ configs.dynamicGasPriceService.priorityFeeCoefficient
+ )
+ )
+
+ val l2SetGasPriceUpdater: GasPriceUpdater = GasPriceUpdaterImpl(
+ httpJsonRpcClientFactory,
+ GasPriceUpdaterImpl.Config(
+ configs.dynamicGasPriceService.minerGasPriceUpdateRecipients
+ )
+ )
+
+ DynamicGasPriceService(
+ DynamicGasPriceService.Config(
+ configs.dynamicGasPriceService.pollingInterval.toKotlinDuration(),
+ configs.dynamicGasPriceService.gasPriceCap
+ ),
+ vertx,
+ gasServiceFeesFetcher,
+ l2MinMinerTipCalculator,
+ l2SetGasPriceUpdater
+ )
+ }
+
+ override fun start(): CompletableFuture {
+ return dynamicGasPriceService.start().thenPeek {
+ log.info("GasPriceUpdater started")
+ }
+ }
+
+ override fun stop(): CompletableFuture {
+ return dynamicGasPriceService.stop()
+ .thenPeek {
+ log.info("GasPriceUpdater stopped")
+ }
+ }
+}
diff --git a/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/app/L1DependentApp.kt b/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/app/L1DependentApp.kt
new file mode 100644
index 000000000..7f58ecdb0
--- /dev/null
+++ b/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/app/L1DependentApp.kt
@@ -0,0 +1,214 @@
+package net.consensys.zkevm.coordinator.app
+
+import io.vertx.core.Vertx
+import kotlinx.datetime.Clock
+import net.consensys.linea.contract.ZkEvmV2AsyncFriendly
+import net.consensys.linea.jsonrpc.client.VertxHttpJsonRpcClientFactory
+import net.consensys.zkevm.LongRunningService
+import net.consensys.zkevm.ethereum.coordination.conflation.Batch
+import net.consensys.zkevm.ethereum.coordination.dynamicgasprice.FeeHistoryFetcherImpl
+import net.consensys.zkevm.ethereum.coordination.dynamicgasprice.FeesCalculator
+import net.consensys.zkevm.ethereum.coordination.dynamicgasprice.FeesFetcher
+import net.consensys.zkevm.ethereum.coordination.dynamicgasprice.WMAFeesCalculator
+import net.consensys.zkevm.ethereum.finalization.FinalizationMonitor
+import net.consensys.zkevm.ethereum.finalization.FinalizationMonitorImpl
+import net.consensys.zkevm.ethereum.settlement.BatchSubmissionCoordinatorService
+import net.consensys.zkevm.ethereum.settlement.BatchSubmitter
+import net.consensys.zkevm.ethereum.settlement.ZkEvmBatchSubmissionCoordinator
+import net.consensys.zkevm.ethereum.settlement.ZkEvmBatchSubmitter
+import net.consensys.zkevm.ethereum.settlement.persistence.BatchesRepository
+import org.apache.logging.log4j.LogManager
+import org.web3j.protocol.Web3j
+import org.web3j.protocol.http.HttpService
+import org.web3j.utils.Async
+import tech.pegasys.teku.infrastructure.async.SafeFuture
+import java.math.BigDecimal
+import java.util.concurrent.CompletableFuture
+import kotlin.time.toKotlinDuration
+
+class L1DependentApp(
+ private val configs: CoordinatorConfig,
+ private val vertx: Vertx,
+ private val l2Web3jClient: Web3j,
+ httpJsonRpcClientFactory: VertxHttpJsonRpcClientFactory,
+ batchesRepository: BatchesRepository,
+ blockFinalizationHandler: (FinalizationMonitor.FinalizationUpdate) -> SafeFuture<*>
+) : LongRunningService {
+ private val log = LogManager.getLogger(this::class.java)
+
+ init {
+ if (configs.messageAnchoringService.disabled) {
+ log.warn("Message anchoring service is disabled")
+ }
+ if (configs.dynamicGasPriceService.disabled) {
+ log.warn("Dynamic gas price service is disabled")
+ }
+ if (configs.batchSubmission.disabled) {
+ log.warn("Batch submission is disabled")
+ }
+ }
+
+ private val l1Web3jClient =
+ Web3j.build(
+ HttpService(configs.l1.rpcEndpoint.toString()),
+ 1000,
+ Async.defaultExecutorService()
+ )
+
+ private val l1TransactionManager = createTransactionManager(vertx, configs.l1Signer, l1Web3jClient)
+
+ private val l1MinMinerTipCalculator: FeesCalculator = WMAFeesCalculator(
+ WMAFeesCalculator.Config(
+ BigDecimal("0.0"),
+ BigDecimal.ONE
+ )
+ )
+
+ private val feesFetcher: FeesFetcher = FeeHistoryFetcherImpl(
+ l1Web3jClient,
+ FeeHistoryFetcherImpl.Config(
+ configs.l1.feeHistoryBlockCount.toUInt(),
+ configs.l1.feeHistoryRewardPercentile
+ )
+ )
+
+ private val finalizationMonitor = run {
+ // To avoid setDefaultBlockParameter clashes
+ val zkEvmClientForFinalization: ZkEvmV2AsyncFriendly = instantiateZkEvmContractClient(
+ configs.l1,
+ l1TransactionManager,
+ feesFetcher,
+ l1MinMinerTipCalculator,
+ l1Web3jClient
+ )
+
+ FinalizationMonitorImpl(
+ config =
+ FinalizationMonitorImpl.Config(
+ configs.l1.finalizationPollingInterval.toKotlinDuration(),
+ configs.l1.blocksToFinalization
+ ),
+ contract = zkEvmClientForFinalization,
+ l1Client = l1Web3jClient,
+ l2Client = l2Web3jClient,
+ vertx = vertx
+ ).apply {
+ addFinalizationHandler("status update", blockFinalizationHandler)
+ }
+ }
+
+ val batchSubmissionCoordinator = run {
+ val zkEvmClientForSubmission: ZkEvmV2AsyncFriendly = instantiateZkEvmContractClient(
+ configs.l1,
+ l1TransactionManager,
+ feesFetcher,
+ l1MinMinerTipCalculator,
+ l1Web3jClient
+ )
+ val batchSubmitter: BatchSubmitter = ZkEvmBatchSubmitter(zkEvmClientForSubmission)
+
+ if (configs.batchSubmission.enabled) {
+ ZkEvmBatchSubmissionCoordinator(
+ ZkEvmBatchSubmissionCoordinator.Config(
+ configs.l1.newBatchPollingInterval.toKotlinDuration(),
+ configs.batchSubmission.proofSubmissionDelay.toKotlinDuration()
+ ),
+ batchSubmitter,
+ batchesRepository,
+ zkEvmClientForSubmission,
+ vertx,
+ Clock.System
+ )
+ } else {
+ // instantiate a dummy batch submitter when is disabled
+ DisabledBatchSubmissionCoordinator()
+ }
+ }
+
+ fun lastFinalizedBlock(): SafeFuture {
+ val zkEvmClient: ZkEvmV2AsyncFriendly = instantiateZkEvmContractClient(
+ configs.l1,
+ l1TransactionManager,
+ feesFetcher,
+ l1MinMinerTipCalculator,
+ l1Web3jClient
+ )
+ val l1BasedLastFinalizedBlockProvider = L1BasedLastFinalizedBlockProvider(
+ vertx,
+ zkEvmClient,
+ configs.conflation.consistentNumberOfBlocksOnL1ToWait.toUInt()
+ )
+
+ return l1BasedLastFinalizedBlockProvider.getLastFinalizedBlock()
+ }
+
+ private val messageAnchoringApp: L1toL2MessageAnchoringApp? = run {
+ if (configs.messageAnchoringService.enabled) {
+ L1toL2MessageAnchoringApp(
+ vertx,
+ L1toL2MessageAnchoringApp.Config(
+ configs.l1,
+ configs.l2,
+ configs.l2Signer,
+ configs.messageAnchoringService
+ ),
+ l1Web3jClient,
+ l2Web3jClient
+ )
+ } else {
+ null
+ }
+ }
+
+ private val gasPriceUpdaterApp: GasPriceUpdaterApp? = if (configs.dynamicGasPriceService.enabled) {
+ GasPriceUpdaterApp(
+ vertx,
+ httpJsonRpcClientFactory,
+ l1Web3jClient,
+ GasPriceUpdaterApp.Config(configs.dynamicGasPriceService)
+ )
+ } else {
+ null
+ }
+
+ override fun start(): CompletableFuture {
+ return finalizationMonitor.start()
+ .thenCompose { batchSubmissionCoordinator.start() }
+ .thenCompose { messageAnchoringApp?.start() ?: SafeFuture.completedFuture(Unit) }
+ .thenCompose { gasPriceUpdaterApp?.start() ?: SafeFuture.completedFuture(Unit) }
+ .thenPeek {
+ log.info("L1App started")
+ }
+ }
+
+ override fun stop(): CompletableFuture {
+ return SafeFuture.allOf(
+ finalizationMonitor.stop(),
+ batchSubmissionCoordinator.stop(),
+ messageAnchoringApp?.stop() ?: SafeFuture.completedFuture(Unit),
+ gasPriceUpdaterApp?.stop() ?: SafeFuture.completedFuture(Unit)
+ )
+ .thenCompose { SafeFuture.fromRunnable { l1Web3jClient.shutdown() } }
+ .thenApply {
+ log.info("L1App Stopped")
+ Unit
+ }
+ }
+}
+
+private class DisabledBatchSubmissionCoordinator : BatchSubmissionCoordinatorService {
+ private val log = LogManager.getLogger(this::class.java)
+
+ override fun acceptNewBatch(batch: Batch): SafeFuture {
+ log.info("Batch submission is disabled. Ignoring batch: {}", batch.intervalString())
+ return SafeFuture.completedFuture(Unit)
+ }
+
+ override fun start(): CompletableFuture {
+ return SafeFuture.completedFuture(Unit)
+ }
+
+ override fun stop(): CompletableFuture {
+ return SafeFuture.completedFuture(Unit)
+ }
+}
diff --git a/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/app/L1toL2MessageAnchoringApp.kt b/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/app/L1toL2MessageAnchoringApp.kt
new file mode 100644
index 000000000..15fb3146f
--- /dev/null
+++ b/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/app/L1toL2MessageAnchoringApp.kt
@@ -0,0 +1,134 @@
+package net.consensys.zkevm.coordinator.app
+
+import io.vertx.core.Vertx
+import net.consensys.linea.contract.AsyncFriendlyTransactionManager
+import net.consensys.linea.contract.L2MessageService
+import net.consensys.zkevm.LongRunningService
+import net.consensys.zkevm.ethereum.EIP1559GasProvider
+import net.consensys.zkevm.ethereum.coordination.messageanchoring.L1EventQuerier
+import net.consensys.zkevm.ethereum.coordination.messageanchoring.L1EventQuerierImpl
+import net.consensys.zkevm.ethereum.coordination.messageanchoring.L2MessageAnchorer
+import net.consensys.zkevm.ethereum.coordination.messageanchoring.L2MessageAnchorerImpl
+import net.consensys.zkevm.ethereum.coordination.messageanchoring.L2Querier
+import net.consensys.zkevm.ethereum.coordination.messageanchoring.L2QuerierImpl
+import net.consensys.zkevm.ethereum.coordination.messageanchoring.MessageAnchoringService
+import org.apache.logging.log4j.LogManager
+import org.web3j.protocol.Web3j
+import tech.pegasys.teku.infrastructure.async.SafeFuture
+import kotlin.time.toKotlinDuration
+
+class L1toL2MessageAnchoringApp(
+ vertx: Vertx,
+ configs: Config,
+ l1Web3jClient: Web3j,
+ l2Web3jClient: Web3j
+) : LongRunningService {
+ private val log = LogManager.getLogger(this::class.java)
+
+ data class Config(
+ val l1: L1Config,
+ val l2: L2Config,
+ val l2Signer: SignerConfig,
+ val messageAnchoringService: MessageAnchoringServiceConfig
+ )
+
+ private val messageAnchoringService: MessageAnchoringService = run {
+ val l1EventQuerier: L1EventQuerier = L1EventQuerierImpl(
+ vertx,
+ L1EventQuerierImpl.Config(
+ configs.l1.sendMessageEventPollingInterval.toKotlinDuration(),
+ configs.l1.maxEventScrapingTime.toKotlinDuration(),
+ configs.l1.earliestBlock,
+ configs.l1.maxMessagesToCollect,
+ configs.l1.zkEvmContractAddress,
+ configs.l1.finalizedBlockTag,
+ configs.l1.blockRangeLoopLimit
+ ),
+ l1Web3jClient
+ )
+
+ val l2TransactionManager = createTransactionManager(
+ vertx,
+ configs.l2Signer,
+ l2Web3jClient
+ )
+
+ val l2MessageService = instantiateL2MessageServiceContractClient(
+ configs.l2,
+ l2TransactionManager,
+ l2Web3jClient
+ )
+
+ val l2Querier: L2Querier = L2QuerierImpl(
+ l2Web3jClient,
+ l2MessageService,
+ L2QuerierImpl.Config(
+ configs.l2.blocksToFinalization,
+ configs.l2.lastHashSearchWindow,
+ configs.l2.lastHashSearchMaxBlocksBack,
+ l2MessageService.contractAddress
+ ),
+ vertx
+ )
+
+ val l2MessageAnchorer: L2MessageAnchorer = L2MessageAnchorerImpl(
+ vertx,
+ l2Web3jClient,
+ l2MessageService,
+ L2MessageAnchorerImpl.Config(
+ configs.l2.anchoringReceiptPollingInterval.toKotlinDuration(),
+ configs.l2.maxReceiptRetries,
+ configs.l2.blocksToFinalization.toLong()
+ )
+ )
+
+ val anchoringConfig = MessageAnchoringService.Config(
+ configs.messageAnchoringService.pollingInterval.toKotlinDuration(),
+ configs.messageAnchoringService.maxMessagesToAnchor
+ )
+
+ MessageAnchoringService(
+ anchoringConfig,
+ vertx,
+ l1EventQuerier,
+ l2MessageAnchorer,
+ l2Querier,
+ l2MessageService,
+ l2TransactionManager
+ )
+ }
+
+ override fun start(): SafeFuture {
+ return messageAnchoringService.start().thenPeek {
+ log.info("L1toL2MessageAnchoringApp started")
+ }
+ }
+
+ override fun stop(): SafeFuture {
+ return messageAnchoringService.stop().thenPeek {
+ log.info("L1toL2MessageAnchoringApp stopped")
+ }
+ }
+
+ private fun instantiateL2MessageServiceContractClient(
+ l2Config: L2Config,
+ transactionManager: AsyncFriendlyTransactionManager,
+ l2Client: Web3j
+ ): L2MessageService {
+ val gasProvider = EIP1559GasProvider(
+ l2Client,
+ EIP1559GasProvider.Config(
+ l2Config.gasLimit,
+ l2Config.maxFeePerGasCap,
+ l2Config.feeHistoryBlockCount,
+ l2Config.feeHistoryRewardPercentile
+ )
+ )
+ return L2MessageService.load(
+ l2Config.messageServiceAddress.toString(),
+ l2Client,
+ transactionManager,
+ gasProvider
+ )
+ }
+}
diff --git a/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/app/LastConflationProvider.kt b/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/app/LastConflationProvider.kt
new file mode 100644
index 000000000..0fdef9bc2
--- /dev/null
+++ b/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/app/LastConflationProvider.kt
@@ -0,0 +1,102 @@
+package net.consensys.zkevm.coordinator.app
+
+import kotlinx.datetime.Instant
+import net.consensys.linea.contract.ZkEvmV2
+import net.consensys.linea.toBigInteger
+import org.apache.logging.log4j.LogManager
+import org.apache.logging.log4j.Logger
+import org.apache.tuweni.units.bigints.UInt256
+import org.web3j.abi.EventEncoder
+import org.web3j.protocol.Web3j
+import org.web3j.protocol.core.DefaultBlockParameter
+import org.web3j.protocol.core.DefaultBlockParameterName
+import org.web3j.protocol.core.methods.request.EthFilter
+import org.web3j.protocol.core.methods.response.EthLog
+import org.web3j.protocol.core.methods.response.Log
+import tech.pegasys.teku.infrastructure.async.SafeFuture
+import java.math.BigInteger
+import java.util.concurrent.CompletableFuture
+
+data class LastConflatedBlock(
+ val l2blockNumber: ULong,
+ val conflationTimestamp: Instant
+)
+
+interface LastConflationProvider {
+ fun getLastConflation(): SafeFuture
+}
+
+/**
+ * This class infers when the last conflation happened based on
+ * the last finalized block on L1, and getting L1 block time stamp;
+ *
+ * It's not a very deterministic/accurate approach, but good enough and avoid managing state in a different database.
+ */
+class L1BasedLastConflationProvider(
+ private val lastFinalizedBlockProvider: LastFinalizedBlockProvider,
+ private val l1Web3j: Web3j,
+ private val l1EarliestBlockNumberToSearchEvents: ULong,
+ private val zkEvmSmartContractWeb3jClient: ZkEvmV2,
+ private val l2Web3j: Web3j
+) : LastConflationProvider {
+ private val log: Logger = LogManager.getLogger(this::class.java)
+ override fun getLastConflation(): SafeFuture {
+ return lastFinalizedBlockProvider.getLastFinalizedBlock()
+ .thenCompose { lastProvenBlockNumber ->
+ if (lastProvenBlockNumber == 0UL) {
+ // beginning of time. Use L1 Genesis block timestamp
+ l2Web3j.ethGetBlockByNumber(DefaultBlockParameter.valueOf(BigInteger.ZERO), false)
+ .sendAsync()
+ .thenApply { block ->
+ Instant.fromEpochSeconds(block.block.timestamp.toLong())
+ }
+ .thenApply { blockTimestamp ->
+ LastConflatedBlock(
+ lastProvenBlockNumber,
+ blockTimestamp
+ )
+ }
+ } else {
+ getLatestFinalizedBlockEmittedEvent(lastProvenBlockNumber)
+ .thenCompose { log ->
+ l1Web3j.ethGetBlockByNumber(DefaultBlockParameter.valueOf(log.blockNumber), false)
+ .sendAsync()
+ .thenApply { block ->
+ Instant.fromEpochSeconds(block.block.timestamp.toLong())
+ }
+ .thenApply { blockTimestamp ->
+ LastConflatedBlock(
+ lastProvenBlockNumber,
+ blockTimestamp
+ )
+ }
+ }
+ }
+ }
+ }
+
+ private fun getLatestFinalizedBlockEmittedEvent(
+ targetFinalizedL2BlockNumber: ULong
+ ): CompletableFuture {
+ val filter = EthFilter(
+ DefaultBlockParameter.valueOf(l1EarliestBlockNumberToSearchEvents.toBigInteger()),
+ DefaultBlockParameterName.LATEST,
+ zkEvmSmartContractWeb3jClient.contractAddress
+ )
+ filter.addSingleTopic(EventEncoder.encode(ZkEvmV2.BLOCKFINALIZED_EVENT))
+ filter.addSingleTopic(UInt256.valueOf(targetFinalizedL2BlockNumber.toLong()).toString())
+ return l1Web3j.ethGetLogs(filter)
+ .sendAsync()
+ .thenCompose { ethLogs: EthLog ->
+ if (ethLogs.logs.isEmpty()) {
+ val errorMessage = "BlockFinalized event not found for block $targetFinalizedL2BlockNumber, " +
+ "between l1 blocks: $l1EarliestBlockNumberToSearchEvents..LATEST"
+ log.error(errorMessage)
+ SafeFuture.failedFuture(Exception(errorMessage))
+ } else {
+ val log: Log = (ethLogs.logs.last() as EthLog.LogObject).get()
+ SafeFuture.completedFuture(log)
+ }
+ }
+ }
+}
diff --git a/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/app/LastFinalizedBlockProvider.kt b/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/app/LastFinalizedBlockProvider.kt
new file mode 100644
index 000000000..faac866a3
--- /dev/null
+++ b/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/app/LastFinalizedBlockProvider.kt
@@ -0,0 +1,75 @@
+package net.consensys.zkevm.coordinator.app
+
+import io.vertx.core.Vertx
+import net.consensys.linea.async.retryWithInterval
+import net.consensys.linea.contract.ZkEvmV2
+import net.consensys.linea.toULong
+import org.apache.logging.log4j.LogManager
+import org.apache.logging.log4j.Logger
+import org.web3j.protocol.core.DefaultBlockParameterName
+import tech.pegasys.teku.infrastructure.async.SafeFuture
+import java.math.BigInteger
+import java.util.concurrent.atomic.AtomicInteger
+import java.util.concurrent.atomic.AtomicReference
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.seconds
+
+interface LastFinalizedBlockProvider {
+ fun getLastFinalizedBlock(): SafeFuture
+}
+
+/**
+ * This class infers when the last conflation happened based on
+ * the last block L1 finalized on L1, and getting L1 block time stamp;
+ *
+ * It's not a very deterministic/accurate approach, but good enough and avoid managing state in a different database.
+ */
+class L1BasedLastFinalizedBlockProvider(
+ private val vertx: Vertx,
+ private val zkEvmSmartContractWeb3jClient: ZkEvmV2,
+ private val consistentNumberOfBlocksOnL1: UInt,
+ private val numberOfRetries: UInt = Int.MAX_VALUE.toUInt(),
+ private val pollingInterval: Duration = 2.seconds
+) : LastFinalizedBlockProvider {
+ private val log: Logger = LogManager.getLogger(this::class.java)
+
+ override fun getLastFinalizedBlock(): SafeFuture {
+ zkEvmSmartContractWeb3jClient.setDefaultBlockParameter(DefaultBlockParameterName.LATEST)
+
+ return SafeFuture.of(zkEvmSmartContractWeb3jClient.currentL2BlockNumber().sendAsync())
+ .thenCompose { blockNumber ->
+ log.info(
+ "Rollup finalized block={}, waiting {} blocks for confirmation for no updates",
+ blockNumber,
+ consistentNumberOfBlocksOnL1
+ )
+ val lastObservedBlock = AtomicReference(blockNumber)
+ val numberOfObservations = AtomicInteger(1)
+ val isConsistentEnough = { lasPolledBlockNumber: BigInteger ->
+ if (lasPolledBlockNumber == lastObservedBlock.get()) {
+ numberOfObservations.incrementAndGet().toUInt() >= consistentNumberOfBlocksOnL1
+ } else {
+ log.info(
+ "Rollup finalized block updated from {} to {}, waiting {} blocks for confirmation",
+ blockNumber,
+ lasPolledBlockNumber,
+ consistentNumberOfBlocksOnL1
+ )
+ numberOfObservations.set(1)
+ lastObservedBlock.set(lasPolledBlockNumber)
+ false
+ }
+ }
+
+ retryWithInterval(
+ numberOfRetries.toInt(),
+ pollingInterval,
+ vertx,
+ isConsistentEnough
+ ) {
+ SafeFuture.of(zkEvmSmartContractWeb3jClient.currentL2BlockNumber().sendAsync())
+ }
+ }
+ .thenApply { it.toULong() }
+ }
+}
diff --git a/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/blockcreation/BlockCreationMonitor.kt b/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/blockcreation/BlockCreationMonitor.kt
new file mode 100644
index 000000000..65ba1710b
--- /dev/null
+++ b/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/blockcreation/BlockCreationMonitor.kt
@@ -0,0 +1,216 @@
+package net.consensys.zkevm.coordinator.blockcreation
+
+import io.vertx.core.TimeoutStream
+import io.vertx.core.Vertx
+import net.consensys.zkevm.ethereum.coordination.blockcreation.BlockCreated
+import net.consensys.zkevm.ethereum.coordination.blockcreation.BlockCreationListener
+import org.apache.logging.log4j.LogManager
+import org.apache.logging.log4j.Logger
+import org.apache.tuweni.bytes.Bytes32
+import org.web3j.protocol.Web3j
+import org.web3j.protocol.core.DefaultBlockParameter
+import org.web3j.protocol.core.Response
+import tech.pegasys.teku.ethereum.executionclient.schema.ExecutionPayloadV1
+import tech.pegasys.teku.infrastructure.async.SafeFuture
+import java.math.BigInteger
+import java.util.concurrent.atomic.AtomicBoolean
+import java.util.concurrent.atomic.AtomicLong
+import java.util.concurrent.atomic.AtomicReference
+import kotlin.time.Duration
+
+/**
+ * Web3J is quite complex to Mock and Unit test, this is just a tinny interface to make
+ * BlockCreationMonitor more testable
+ */
+interface ExtendedWeb3J {
+ val web3jClient: Web3j
+ fun ethBlockNumber(): SafeFuture
+ fun ethGetExecutionPayloadByNumber(blockNumber: Long): SafeFuture
+}
+
+class ExtendedWeb3JImpl(override val web3jClient: Web3j) : ExtendedWeb3J {
+
+ private fun buildException(error: Response.Error): Exception =
+ Exception("${error.code}: ${error.message}")
+
+ override fun ethBlockNumber(): SafeFuture {
+ return SafeFuture.of(web3jClient.ethBlockNumber().sendAsync()).thenCompose { response ->
+ if (response.hasError()) {
+ SafeFuture.failedFuture(buildException(response.error))
+ } else {
+ SafeFuture.completedFuture(response.blockNumber)
+ }
+ }
+ }
+
+ override fun ethGetExecutionPayloadByNumber(blockNumber: Long): SafeFuture {
+ return SafeFuture.of(
+ web3jClient
+ .ethGetBlockByNumber(
+ DefaultBlockParameter.valueOf(BigInteger.valueOf(blockNumber)),
+ true
+ )
+ .sendAsync()
+ )
+ .thenCompose { response ->
+ if (response.hasError()) {
+ SafeFuture.failedFuture(buildException(response.error))
+ } else {
+ SafeFuture.completedFuture(response.block.toExecutionPayloadV1())
+ }
+ }
+ }
+}
+
+class BlockCreationMonitor(
+ private val vertx: Vertx,
+ private val extendedWeb3j: ExtendedWeb3J,
+ startingBlockNumberInclusive: Long,
+ expectedParentRooHash: Bytes32,
+ private val blockCreationListener: BlockCreationListener,
+ private val config: Config,
+ private val log: Logger = LogManager.getLogger(BlockCreationMonitor::class.java)
+) {
+ data class Config(
+ val pollingInterval: Duration,
+ val blocksToFinalization: Long
+ )
+
+ private val _nexBlockNumberToFetch: AtomicLong = AtomicLong(startingBlockNumberInclusive)
+ private val expectedParentRooHash: AtomicReference = AtomicReference(expectedParentRooHash)
+ private val reorgDetected: AtomicBoolean = AtomicBoolean(false)
+
+ @Volatile
+ private lateinit var monitorStream: TimeoutStream
+
+ val nexBlockNumberToFetch: Long
+ get() = _nexBlockNumberToFetch.get()
+
+ @Synchronized
+ fun start(): SafeFuture {
+ if (reorgDetected.get()) {
+ return SafeFuture.failedFuture(IllegalStateException("Reorg detect. Cannot restart"))
+ }
+ if (this::monitorStream.isInitialized) {
+ this.monitorStream.cancel()
+ }
+
+ monitorStream =
+ vertx.periodicStream(config.pollingInterval.inWholeMilliseconds.coerceAtLeast(1L))
+ .handler {
+ try {
+ monitorStream.pause()
+ tick().whenComplete { _, _ ->
+ contunueOrStopIfReorg()
+ }
+ } catch (th: Throwable) {
+ log.error(th)
+ contunueOrStopIfReorg()
+ }
+ }
+
+ return SafeFuture.completedFuture(Unit)
+ }
+
+ @Synchronized
+ fun stop(): SafeFuture {
+ if (this::monitorStream.isInitialized) {
+ this.monitorStream.cancel()
+ }
+ return SafeFuture.completedFuture(Unit)
+ }
+
+ private fun tick(): SafeFuture<*> {
+ log.trace("tick start")
+ return getNetNextSafeBlock()
+ .thenCompose { payload ->
+ if (payload != null) {
+ if (payload.parentHash == expectedParentRooHash.get()) {
+ notifyListener(payload)
+ .whenSuccess {
+ log.debug(
+ "updating nexBlockNumberToFetch from {} --> {}",
+ _nexBlockNumberToFetch.get(),
+ _nexBlockNumberToFetch.incrementAndGet()
+ )
+ expectedParentRooHash.set(payload.blockHash)
+ }
+ } else {
+ reorgDetected.set(true)
+ log.error(
+ "Shooting down conflation poller, " +
+ "chain reorg detected: block (number={}, hash={}, parentHash={}) should have parentHash={}",
+ payload.blockNumber.longValue(),
+ payload.blockHash.toHexString().subSequence(0, 8),
+ payload.parentHash.toHexString().subSequence(0, 8),
+ expectedParentRooHash.get().toHexString().subSequence(0, 8)
+ )
+ SafeFuture.failedFuture(IllegalStateException("Reorg detected on block ${payload.blockNumber}"))
+ }
+ } else {
+ SafeFuture.completedFuture(Unit)
+ }
+ }
+ .whenComplete { _, error ->
+ log.error("Block creation monitor failed: errorMessage={}", error.message, error)
+ log.trace("tick end")
+ }
+ }
+
+ private fun contunueOrStopIfReorg() {
+ if (!reorgDetected.get()) {
+ monitorStream.resume()
+ } else {
+ stop()
+ }
+ }
+
+ private fun notifyListener(payload: ExecutionPayloadV1): SafeFuture {
+ return blockCreationListener.acceptBlock(BlockCreated(payload))
+ .thenApply {
+ log.debug(
+ "blockCreationListener blockNumber={} resolved with success",
+ payload.blockNumber
+ )
+ }
+ .whenException { throwable ->
+ log.warn(
+ "Failed to notify blockCreationListener: blockNumber={} errorMessage={}",
+ payload.blockNumber.bigIntegerValue(),
+ throwable.message,
+ throwable
+ )
+ }
+ }
+
+ private fun getNetNextSafeBlock(): SafeFuture {
+ return extendedWeb3j
+ .ethBlockNumber()
+ .whenException { log.error("eth_blockNumber failed: errorMessage={}", it.message, it) }
+ .thenCompose { latestBlockNumber ->
+ // Check if is safe to fetch nextWaitingBlockNumber
+ log.trace(
+ "nexBlockNumberToFetch={}, blocksToFinalization={}, latestBlockNumber={}",
+ _nexBlockNumberToFetch.get(),
+ config.blocksToFinalization,
+ latestBlockNumber
+ )
+ if (latestBlockNumber.toLong() >=
+ _nexBlockNumberToFetch.get() + config.blocksToFinalization
+ ) {
+ val blockNumber = _nexBlockNumberToFetch.get()
+ extendedWeb3j.ethGetExecutionPayloadByNumber(blockNumber)
+ .whenException {
+ log.error(
+ "eth_getBlockByNumber({}) failed: errorMessage={}",
+ blockNumber,
+ it.message,
+ it
+ )
+ }
+ } else {
+ SafeFuture.completedFuture(null)
+ }
+ }
+ }
+}
diff --git a/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/blockcreation/DomainObjectMappers.kt b/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/blockcreation/DomainObjectMappers.kt
new file mode 100644
index 000000000..a72aca657
--- /dev/null
+++ b/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/blockcreation/DomainObjectMappers.kt
@@ -0,0 +1,146 @@
+package net.consensys.zkevm.coordinator.blockcreation
+
+import org.apache.logging.log4j.LogManager
+import org.apache.logging.log4j.Logger
+import org.apache.tuweni.bytes.Bytes
+import org.apache.tuweni.bytes.Bytes32
+import org.apache.tuweni.units.bigints.UInt256
+import org.bouncycastle.math.ec.custom.sec.SecP256K1Curve
+import org.hyperledger.besu.crypto.SECPSignature
+import org.hyperledger.besu.datatypes.Address
+import org.hyperledger.besu.datatypes.Wei
+import org.hyperledger.besu.ethereum.core.Transaction
+import org.hyperledger.besu.ethereum.core.encoding.TransactionEncoder
+import org.hyperledger.besu.evm.AccessListEntry
+import org.web3j.protocol.core.methods.response.EthBlock
+import tech.pegasys.teku.ethereum.executionclient.schema.ExecutionPayloadV1
+import tech.pegasys.teku.infrastructure.bytes.Bytes20
+import tech.pegasys.teku.infrastructure.unsigned.UInt64
+import java.math.BigInteger
+
+private val log: Logger = LogManager.getLogger("DomainObjectMappers")
+fun EthBlock.Block.toExecutionPayloadV1(): ExecutionPayloadV1 {
+ /**
+ * @JsonProperty("parentHash") Bytes32 parentHash,
+ * @JsonProperty("feeRecipient") Bytes20 feeRecipient,
+ * @JsonProperty("stateRoot") Bytes32 stateRoot,
+ * @JsonProperty("receiptsRoot") Bytes32 receiptsRoot,
+ * @JsonProperty("logsBloom") Bytes logsBloom,
+ * @JsonProperty("prevRandao") Bytes32 prevRandao,
+ * @JsonProperty("blockNumber") UInt64 blockNumber,
+ * @JsonProperty("gasLimit") UInt64 gasLimit,
+ * @JsonProperty("gasUsed") UInt64 gasUsed,
+ * @JsonProperty("timestamp") UInt64 timestamp,
+ * @JsonProperty("extraData") Bytes extraData,
+ * @JsonProperty("baseFeePerGas") UInt256 baseFeePerGas,
+ * @JsonProperty("blockHash") Bytes32 blockHash,
+ * @JsonProperty("transactions") List transactions)
+ */
+ return ExecutionPayloadV1(
+ Bytes32.fromHexString(this.parentHash),
+ Bytes20.fromHexString(this.miner),
+ Bytes32.fromHexString(this.stateRoot),
+ Bytes32.fromHexString(this.receiptsRoot),
+ Bytes.fromHexString(this.logsBloom),
+ Bytes32.fromHexString(this.mixHash),
+ UInt64.valueOf(this.number),
+ UInt64.valueOf(this.gasLimit),
+ UInt64.valueOf(this.gasUsed),
+ UInt64.valueOf(this.timestamp),
+ Bytes.fromHexString(this.extraData),
+ UInt256.valueOf(this.baseFeePerGas),
+ Bytes32.fromHexString(this.hash),
+ this.transactions.map {
+ val transaction = it.get() as EthBlock.TransactionObject
+ kotlin.runCatching {
+ transaction.toBytes()
+ }.onFailure { th ->
+ log.error(
+ "Failed to encode transaction! blockNumber={}, tx={}, errorMessage={}",
+ this.number,
+ transaction.hash.toString(),
+ th.message,
+ th
+ )
+ }
+ .getOrThrow()
+ }
+ )
+}
+
+fun recIdFromV(v: BigInteger): Pair {
+ val recId: Byte
+ var chainId: BigInteger? = null
+ if (v == Transaction.REPLAY_UNPROTECTED_V_BASE || v == Transaction.REPLAY_UNPROTECTED_V_BASE_PLUS_1) {
+ recId = v.subtract(Transaction.REPLAY_UNPROTECTED_V_BASE).byteValueExact()
+ } else if (v > Transaction.REPLAY_PROTECTED_V_MIN) {
+ chainId = v.subtract(Transaction.REPLAY_PROTECTED_V_BASE).divide(Transaction.TWO)
+ recId = v.subtract(Transaction.TWO.multiply(chainId).add(Transaction.REPLAY_PROTECTED_V_BASE)).byteValueExact()
+ } else {
+ throw RuntimeException("An unsupported encoded `v` value of $v was found")
+ }
+ return Pair(recId, chainId)
+}
+
+// TODO: Test
+fun EthBlock.TransactionObject.toBytes(): Bytes {
+ val isFrontier = this.type == "0x0"
+ val (recId, chainId) = if (isFrontier) {
+ recIdFromV(this.v.toBigInteger())
+ } else {
+ Pair(this.v.toByte(), BigInteger.valueOf(this.chainId))
+ }
+ val signature = SECPSignature.create(
+ this.r.bigIntFromPrefixedHex(),
+ this.s.bigIntFromPrefixedHex(),
+ recId,
+ SecP256K1Curve().order
+ )
+
+ val transaction = Transaction.builder()
+ .nonce(this.nonce.toLong())
+ .also { builder ->
+ if (isFrontier || this.type == "0x1") {
+ builder.gasPrice(Wei.of(this.gasPrice))
+ } else {
+ builder.maxPriorityFeePerGas(Wei.of(this.maxPriorityFeePerGas))
+ builder.maxFeePerGas(Wei.of(this.maxFeePerGas))
+ }
+ }
+ .gasLimit(this.gas.toLong())
+ .to(Address.fromHexString(this.to))
+ .value(Wei.of(this.value))
+ .signature(signature)
+ .payload(Bytes.fromHexString(this.input))
+ .also { builder ->
+ this.accessList?.also { accessList ->
+ builder.accessList(
+ accessList.map { entry ->
+ AccessListEntry.createAccessListEntry(
+ Address.fromHexString(entry.address),
+ entry.storageKeys
+ )
+ }
+ )
+ }
+ }
+ .sender(Address.fromHexString(this.from))
+ .apply {
+ if (chainId != null) {
+ chainId(chainId)
+ }
+ }
+ .build()
+
+ if (signature != null) {
+ if (this.v != transaction.v.toLong()) {
+ throw RuntimeException("Transactions v values do not match: original=${this.v}, inferred=${transaction.v}")
+ }
+ }
+
+ return TransactionEncoder.encodeOpaqueBytes(transaction)
+}
+
+fun String.bigIntFromPrefixedHex(): BigInteger {
+ return BigInteger(this.removePrefix("0x"), 16)
+}
diff --git a/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/blockcreation/GethCliqueSafeBlockProvider.kt b/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/blockcreation/GethCliqueSafeBlockProvider.kt
new file mode 100644
index 000000000..cccc1abef
--- /dev/null
+++ b/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/blockcreation/GethCliqueSafeBlockProvider.kt
@@ -0,0 +1,26 @@
+package net.consensys.zkevm.coordinator.blockcreation
+
+import net.consensys.zkevm.ethereum.coordination.blockcreation.SafeBlockProvider
+import org.web3j.protocol.core.DefaultBlockParameterName
+import tech.pegasys.teku.ethereum.executionclient.schema.ExecutionPayloadV1
+import tech.pegasys.teku.infrastructure.async.SafeFuture
+
+class GethCliqueSafeBlockProvider(
+ private val extendedWeb3j: ExtendedWeb3J,
+ private val config: Config
+) : SafeBlockProvider {
+ data class Config(
+ val blocksToFinalization: Long
+ )
+
+ override fun getLatestSafeBlock(): SafeFuture {
+ return SafeFuture.of(
+ extendedWeb3j.web3jClient
+ .ethGetBlockByNumber(DefaultBlockParameterName.LATEST, false).sendAsync()
+ )
+ .thenCompose { block ->
+ val safeBlockNumber = (block.block.number.toLong() - config.blocksToFinalization).coerceAtLeast(0)
+ extendedWeb3j.ethGetExecutionPayloadByNumber(safeBlockNumber)
+ }
+ }
+}
diff --git a/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/blockcreation/TracesFilesManager.kt b/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/blockcreation/TracesFilesManager.kt
new file mode 100644
index 000000000..818fa3756
--- /dev/null
+++ b/coordinator/app/src/main/kotlin/net/consensys/zkevm/coordinator/blockcreation/TracesFilesManager.kt
@@ -0,0 +1,109 @@
+package net.consensys.zkevm.coordinator.blockcreation
+
+import io.vertx.core.CompositeFuture
+import io.vertx.core.Vertx
+import net.consensys.linea.async.retryWithInterval
+import net.consensys.linea.async.toSafeFuture
+import net.consensys.linea.traces.TracesFileNameSupplier
+import net.consensys.linea.traces.TracesFiles
+import net.consensys.zkevm.coordinator.clients.TracesWatcher
+import org.apache.logging.log4j.LogManager
+import org.apache.logging.log4j.Logger
+import org.apache.tuweni.bytes.Bytes32
+import tech.pegasys.teku.infrastructure.async.SafeFuture
+import tech.pegasys.teku.infrastructure.unsigned.UInt64
+import java.io.FileNotFoundException
+import java.nio.file.Files
+import java.nio.file.Path
+import kotlin.time.Duration
+
+class TracesFilesManager(
+ private val vertx: Vertx,
+ private val config: Config,
+ private val tracesFileNameSupplier: TracesFileNameSupplier = TracesFiles::rawTracesFileNameSupplierV1
+) : TracesWatcher {
+ data class Config(
+ val tracesFolder: Path,
+ val nonCanonicalTracesDir: Path,
+ val pollingInterval: Duration,
+ val tracesGenerationTimeout: Duration,
+ val tracesEngineVersion: String,
+ val tracesFileExtension: String,
+ val createNonCanonicalTracesDirIfDoesNotExist: Boolean
+ )
+
+ private val log: Logger = LogManager.getLogger(this::class.java)
+ private val retries: Int = run {
+ config.tracesGenerationTimeout.inWholeMilliseconds /
+ config.pollingInterval.inWholeMilliseconds.coerceAtLeast(1L)
+ }.toInt()
+
+ init {
+ if (!Files.exists(config.nonCanonicalTracesDir)) {
+ if (config.createNonCanonicalTracesDirIfDoesNotExist) {
+ Files.createDirectories(config.nonCanonicalTracesDir)
+ } else {
+ throw FileNotFoundException("${config.nonCanonicalTracesDir} directory not found!")
+ }
+ }
+ }
+
+ override fun waitRawTracesGenerationOf(
+ blockNumber: UInt64,
+ blockHash: Bytes32
+ ): SafeFuture {
+ val fileName =
+ tracesFileNameSupplier(
+ blockNumber.longValue().toULong(),
+ blockHash,
+ config.tracesEngineVersion,
+ config.tracesFileExtension
+ )
+ val targetFile = config.tracesFolder.resolve(fileName).toFile()
+ log.trace("Waiting for traces file: ${targetFile.absolutePath}")
+ return retryWithInterval(retries, config.pollingInterval, vertx) {
+ log.trace("Waiting for traces file: ${targetFile.absolutePath}")
+ if (targetFile.exists()) {
+ log.trace("Found for traces file: ${targetFile.absolutePath}")
+ SafeFuture.completedFuture(targetFile.absolutePath)
+ } else {
+ val errorMessage = "File matching '$fileName' not found after ${config.tracesGenerationTimeout}."
+ SafeFuture.failedFuture(FileNotFoundException(errorMessage))
+ }
+ }.whenException {
+ log.error(it.message)
+ }
+ }
+
+ internal fun cleanNonCanonicalSiblingsByHeight(
+ blockNumber: UInt64,
+ canonicalBlockHashToKeep: Bytes32
+ ): SafeFuture> {
+ return vertx
+ .fileSystem()
+ .readDir(config.tracesFolder.toString())
+ .flatMap { listOfFiles ->
+ val filesToMove =
+ listOfFiles.filter { fileAbsolutePath ->
+ val fileName = Path.of(fileAbsolutePath).fileName.toString().lowercase()
+ fileName.startsWith("$blockNumber-") &&
+ fileName.endsWith(config.tracesFileExtension.lowercase()) &&
+ !fileName.contains(canonicalBlockHashToKeep.toHexString().lowercase())
+ }
+
+ CompositeFuture.all(
+ filesToMove.map { fileAbsolutePath ->
+ val destination =
+ config.nonCanonicalTracesDir
+ .resolve(Path.of(fileAbsolutePath).fileName)
+ .toString()
+ log.info("Moving non-canonical traces file $fileAbsolutePath --> $destination")
+ vertx.fileSystem().move(fileAbsolutePath, destination)
+ }
+ )
+ .map { filesToMove }
+ }
+ .toSafeFuture()
+ .whenException { th -> log.error("Failed to move traces files: errorMessage={}", th.message, th) }
+ }
+}
diff --git a/coordinator/app/src/main/kotlin/net/consensys/zkevm/ethereum/EIP1559GasProvider.kt b/coordinator/app/src/main/kotlin/net/consensys/zkevm/ethereum/EIP1559GasProvider.kt
new file mode 100644
index 000000000..361a5c774
--- /dev/null
+++ b/coordinator/app/src/main/kotlin/net/consensys/zkevm/ethereum/EIP1559GasProvider.kt
@@ -0,0 +1,118 @@
+package net.consensys.zkevm.ethereum
+
+import net.consensys.linea.toIntervalString
+import net.consensys.linea.web3j.blocksRange
+import org.apache.logging.log4j.LogManager
+import org.apache.logging.log4j.Logger
+import org.web3j.protocol.Web3j
+import org.web3j.protocol.core.DefaultBlockParameterName
+import org.web3j.tx.gas.ContractEIP1559GasProvider
+import java.math.BigDecimal
+import java.math.BigInteger
+
+class EIP1559GasProvider(private val l2Client: Web3j, private val config: Config) :
+ ContractEIP1559GasProvider {
+ private val log: Logger = LogManager.getLogger(this::class.java)
+
+ data class Config(
+ val gasLimit: BigInteger,
+ val maxFeePerGasCap: BigInteger,
+ val feeHistoryBlockCount: UInt,
+ val feeHistoryRewardPercentile: Double
+ )
+ private data class Fees(
+ val maxFeePerGas: BigInteger,
+ val maxPriorityFeePerGas: BigInteger
+ )
+
+ private val chainId: Long = l2Client.ethChainId().send().chainId.toLong()
+ private var cacheIsValidForBlockNumber: BigInteger = BigInteger.ZERO
+ private var feesCache: Fees = getRecentFees()
+
+ private fun getRecentFees(): Fees {
+ val currentBlockNumber = l2Client.ethBlockNumber().send().blockNumber
+ if (currentBlockNumber > cacheIsValidForBlockNumber) {
+ l2Client
+ .ethFeeHistory(
+ config.feeHistoryBlockCount.toInt(),
+ DefaultBlockParameterName.LATEST,
+ listOf(config.feeHistoryRewardPercentile)
+ )
+ .sendAsync()
+ .thenApply {
+ feeHistoryResponse ->
+ var maxPriorityFeePerGas = feeHistoryResponse.feeHistory.reward.map { BigDecimal(it[0]) }
+ .reduce { acc, reward ->
+ acc.add(reward)
+ }.divide(BigDecimal(feeHistoryResponse.feeHistory.reward.size)).toBigInteger()
+
+ if (maxPriorityFeePerGas > config.maxFeePerGasCap) {
+ maxPriorityFeePerGas = config.maxFeePerGasCap
+ log.warn(
+ "Estimated miner tip of $maxPriorityFeePerGas exceeds configured max " +
+ "fee per gas of ${config.maxFeePerGasCap} returning cap instead!"
+ )
+ }
+
+ cacheIsValidForBlockNumber = currentBlockNumber
+
+ val maxFeePerGas = feeHistoryResponse.feeHistory.baseFeePerGas.last()
+ .multiply(BigInteger.TWO)
+ .add(maxPriorityFeePerGas)
+
+ if (maxFeePerGas > BigInteger.ZERO && maxPriorityFeePerGas > BigInteger.ZERO) {
+ feesCache = Fees(
+ maxFeePerGas.min(config.maxFeePerGasCap),
+ maxPriorityFeePerGas
+ )
+ log.debug(
+ "New fees estimation: fees={}, l2Blocks={}",
+ feeHistoryResponse.feeHistory.blocksRange().toIntervalString(),
+ feesCache
+ )
+ } else {
+ feesCache = Fees(
+ config.maxFeePerGasCap,
+ BigInteger.ZERO
+ )
+ }
+ }
+ .get()
+ }
+ return feesCache
+ }
+
+ override fun getGasPrice(contractFunc: String?): BigInteger {
+ return getGasPrice()
+ }
+
+ @Deprecated("Deprecated in Java")
+ override fun getGasPrice(): BigInteger {
+ throw NotImplementedError("EIP1559GasProvider only implements EIP1559 specific methods")
+ }
+
+ override fun getGasLimit(contractFunc: String?): BigInteger {
+ return getGasLimit()
+ }
+
+ @Deprecated("Deprecated in Java")
+ override fun getGasLimit(): BigInteger {
+ return config.gasLimit
+ }
+
+ override fun isEIP1559Enabled(): Boolean {
+ return true
+ }
+
+ override fun getChainId(): Long {
+ return chainId
+ }
+
+ override fun getMaxFeePerGas(contractFunc: String?): BigInteger {
+ return getRecentFees().maxFeePerGas
+ }
+
+ override fun getMaxPriorityFeePerGas(contractFunc: String?): BigInteger {
+ return getRecentFees().maxPriorityFeePerGas
+ }
+}
diff --git a/coordinator/app/src/main/kotlin/net/consensys/zkevm/ethereum/coordination/dynamicgasprice/DynamicGasPriceService.kt b/coordinator/app/src/main/kotlin/net/consensys/zkevm/ethereum/coordination/dynamicgasprice/DynamicGasPriceService.kt
new file mode 100644
index 000000000..16877d2e6
--- /dev/null
+++ b/coordinator/app/src/main/kotlin/net/consensys/zkevm/ethereum/coordination/dynamicgasprice/DynamicGasPriceService.kt
@@ -0,0 +1,80 @@
+package net.consensys.zkevm.ethereum.coordination.dynamicgasprice
+
+import io.vertx.core.TimeoutStream
+import io.vertx.core.Vertx
+import net.consensys.linea.toGWei
+import net.consensys.linea.toIntervalString
+import org.apache.logging.log4j.LogManager
+import org.apache.logging.log4j.Logger
+import tech.pegasys.teku.infrastructure.async.SafeFuture
+import java.math.BigInteger
+import kotlin.time.Duration
+
+class DynamicGasPriceService(
+ private val config: Config,
+ private val vertx: Vertx,
+ private val feesFetcher: FeesFetcher,
+ private val feesCalculator: FeesCalculator,
+ private val gasPriceUpdater: GasPriceUpdater
+) {
+ class Config(
+ val pollingInterval: Duration,
+ val gasPriceCap: BigInteger
+ )
+
+ private val log: Logger = LogManager.getLogger(this::class.java)
+
+ @Volatile
+ private lateinit var monitorStream: TimeoutStream
+
+ internal fun tick(): SafeFuture {
+ return feesFetcher
+ .getL1EthGasPriceData()
+ .thenCompose { feeHistory ->
+ val blockRange = feeHistory.blocksRange()
+ val gasPrice = feesCalculator.calculateFees(feeHistory)
+ val gasPriceToUpdate = if (gasPrice > config.gasPriceCap) {
+ log.warn(
+ "L2 Gas price update: gasPrice={}GWei, l1Blocks={}. gasPrice is higher than gasPriceCap={}GWei." +
+ " Will default to cap value",
+ gasPrice.toGWei(),
+ blockRange.toIntervalString(),
+ config.gasPriceCap.toGWei()
+ )
+ config.gasPriceCap
+ } else {
+ log.info(
+ "L2 Gas price update: gasPrice={}GWei, l1Blocks={}.",
+ gasPrice.toGWei(),
+ blockRange.toIntervalString()
+ )
+ gasPrice
+ }
+ gasPriceUpdater.updateMinerGasPrice(gasPriceToUpdate)
+ }
+ .thenApply { log.debug("Fetch, calculate, update new miner gas price are all done.") }
+ }
+
+ fun start(): SafeFuture {
+ monitorStream =
+ vertx.periodicStream(config.pollingInterval.inWholeMilliseconds).handler {
+ try {
+ monitorStream.pause()
+ tick().whenComplete { _, _ -> monitorStream.resume() }
+ } catch (th: Throwable) {
+ log.error(th)
+ monitorStream.resume()
+ }
+ }
+ return SafeFuture.completedFuture(Unit)
+ }
+
+ fun stop(): SafeFuture {
+ return if (this::monitorStream.isInitialized) {
+ SafeFuture.completedFuture(monitorStream.cancel())
+ } else {
+ log.warn("Dynamic Gas Price Service hasn't been started to stop it, but Ok")
+ SafeFuture.completedFuture(Unit)
+ }
+ }
+}
diff --git a/coordinator/app/src/main/kotlin/net/consensys/zkevm/ethereum/coordination/dynamicgasprice/FeeHistoryFetcherImpl.kt b/coordinator/app/src/main/kotlin/net/consensys/zkevm/ethereum/coordination/dynamicgasprice/FeeHistoryFetcherImpl.kt
new file mode 100644
index 000000000..63ef29307
--- /dev/null
+++ b/coordinator/app/src/main/kotlin/net/consensys/zkevm/ethereum/coordination/dynamicgasprice/FeeHistoryFetcherImpl.kt
@@ -0,0 +1,73 @@
+package net.consensys.zkevm.ethereum.coordination.dynamicgasprice
+
+import net.consensys.linea.FeeHistory
+import net.consensys.linea.web3j.toLineaDomain
+import org.apache.logging.log4j.LogManager
+import org.apache.logging.log4j.Logger
+import org.web3j.protocol.Web3j
+import org.web3j.protocol.core.DefaultBlockParameterName
+import tech.pegasys.teku.infrastructure.async.SafeFuture
+import java.math.BigInteger
+
+class FeeHistoryFetcherImpl(
+ private val web3jClient: Web3j,
+ private val config: Config
+) : FeesFetcher {
+ private val log: Logger = LogManager.getLogger(this::class.java)
+
+ data class Config(
+ val feeHistoryBlockCount: UInt,
+ val feeHistoryRewardPercentile: Double
+ ) {
+ init {
+ require(feeHistoryRewardPercentile in 0.0..100.0) {
+ throw IllegalArgumentException(
+ "feeHistoryRewardPercentile must be within 0.0 and 100.0." +
+ " Value=$feeHistoryRewardPercentile"
+ )
+ }
+ }
+ }
+
+ private var cacheIsValidForBlockNumber: BigInteger = BigInteger.ZERO
+ private var feesCache: FeeHistory = getRecentFees().get()
+
+ private fun getRecentFees(): SafeFuture {
+ val blockNumberFuture = web3jClient.ethBlockNumber().sendAsync()
+ return SafeFuture.of(blockNumberFuture)
+ .thenCompose { blockNumberResponse ->
+ val currentBlockNumber = blockNumberResponse.blockNumber
+ if (currentBlockNumber > cacheIsValidForBlockNumber) {
+ web3jClient
+ .ethFeeHistory(
+ config.feeHistoryBlockCount.toInt(),
+ DefaultBlockParameterName.LATEST,
+ listOf(config.feeHistoryRewardPercentile)
+ )
+ .sendAsync()
+ .thenApply { it ->
+ val feeHistory = it.feeHistory.toLineaDomain()
+ cacheIsValidForBlockNumber = currentBlockNumber
+ log.trace(
+ "New Fee History: l1BlockNumber={}, lastBaseFeePerGas={} reward={}, gasUsedRatio={}",
+ currentBlockNumber,
+ feeHistory.baseFeePerGas[feeHistory.baseFeePerGas.lastIndex - 1],
+ feeHistory.reward.map { percentiles -> percentiles[0] },
+ feeHistory.gasUsedRatio
+ )
+ feesCache = feeHistory
+ feesCache
+ }
+ } else {
+ SafeFuture.completedFuture(feesCache)
+ }
+ }
+ }
+
+ override fun getL1EthGasPriceData(): SafeFuture {
+ return getRecentFees()
+ .whenException { th ->
+ log.error("Get L1 gas price data failure: {}", th.message, th)
+ }
+ }
+}
diff --git a/coordinator/app/src/main/kotlin/net/consensys/zkevm/ethereum/coordination/dynamicgasprice/GasPriceUpdaterImpl.kt b/coordinator/app/src/main/kotlin/net/consensys/zkevm/ethereum/coordination/dynamicgasprice/GasPriceUpdaterImpl.kt
new file mode 100644
index 000000000..6de3da794
--- /dev/null
+++ b/coordinator/app/src/main/kotlin/net/consensys/zkevm/ethereum/coordination/dynamicgasprice/GasPriceUpdaterImpl.kt
@@ -0,0 +1,71 @@
+package net.consensys.zkevm.ethereum.coordination.dynamicgasprice
+
+import com.github.michaelbull.result.onFailure
+import com.github.michaelbull.result.onSuccess
+import net.consensys.linea.async.toSafeFuture
+import net.consensys.linea.jsonrpc.BaseJsonRpcRequest
+import net.consensys.linea.jsonrpc.client.JsonRpcClient
+import net.consensys.linea.jsonrpc.client.VertxHttpJsonRpcClientFactory
+import org.apache.logging.log4j.Level
+import org.apache.logging.log4j.LogManager
+import org.apache.logging.log4j.Logger
+import tech.pegasys.teku.infrastructure.async.SafeFuture
+import java.math.BigInteger
+import java.net.URL
+import java.util.concurrent.atomic.AtomicInteger
+
+class GasPriceUpdaterImpl(
+ private val httpJsonRpcClientFactory: VertxHttpJsonRpcClientFactory,
+ config: Config
+) : GasPriceUpdater {
+ class Config(
+ val endpoints: List
+ )
+
+ private val log: Logger = LogManager.getLogger(this::class.java)
+ private val rpcClients: List = config.endpoints.map {
+ httpJsonRpcClientFactory.create(
+ it,
+ log = log,
+ requestDefaultLogLevel = Level.TRACE,
+ requestFailureLogLevel = Level.DEBUG
+ )
+ }
+ private var id = AtomicInteger(0)
+
+ override fun updateMinerGasPrice(gasPrice: BigInteger): SafeFuture> {
+ val jsonRequest =
+ BaseJsonRpcRequest(
+ "2.0",
+ id.incrementAndGet(),
+ "miner_setGasPrice",
+ listOf(
+ "0x${gasPrice.toString(16)}"
+ )
+ )
+
+ return SafeFuture.collectAll(
+ rpcClients.map { jsonRpcClient ->
+ jsonRpcClient
+ .makeRequest(jsonRequest)
+ .toSafeFuture()
+ .whenException { th ->
+ log.error("Error from rpc request of miner_setGasPrice: errorMessage={}", th.message, th)
+ }
+ .thenApply { result ->
+ result
+ .onSuccess {
+ if (it.result == true) {
+ log.trace("Result of miner_setGasPrice: {}", it.result)
+ } else {
+ log.warn("Result of miner_setGasPrice: {}", it.result)
+ }
+ }.onFailure {
+ log.error("Error from miner_setGasPrice: errorMessage={}", it.error)
+ }
+ Unit
+ }
+ }.stream()
+ )
+ }
+}
diff --git a/coordinator/app/src/main/kotlin/net/consensys/zkevm/ethereum/coordination/dynamicgasprice/GasUsageRatioWeightedAverageFeesCalculator.kt b/coordinator/app/src/main/kotlin/net/consensys/zkevm/ethereum/coordination/dynamicgasprice/GasUsageRatioWeightedAverageFeesCalculator.kt
new file mode 100644
index 000000000..ac1d6dc8e
--- /dev/null
+++ b/coordinator/app/src/main/kotlin/net/consensys/zkevm/ethereum/coordination/dynamicgasprice/GasUsageRatioWeightedAverageFeesCalculator.kt
@@ -0,0 +1,60 @@
+package net.consensys.zkevm.ethereum.coordination.dynamicgasprice
+
+import net.consensys.linea.FeeHistory
+import net.consensys.linea.toIntervalString
+import org.apache.logging.log4j.LogManager
+import org.apache.logging.log4j.Logger
+import java.math.BigDecimal
+import java.math.BigInteger
+import java.math.MathContext
+
+/**
+ * Gas Ratio Weighted Average Fees Calculator
+ * L2GasPrice = sumOf((baseFee[i]*baseFeeCoefficient + reward[i]*priorityFeeWmaCoefficient)*ratio[i])/sumOf(ratio[i])
+ */
+class GasUsageRatioWeightedAverageFeesCalculator(
+ val config: Config
+) : FeesCalculator {
+ data class Config(
+ val baseFeeCoefficient: BigDecimal,
+ val priorityFeeCoefficient: BigDecimal
+ )
+
+ private val log: Logger = LogManager.getLogger(this::class.java)
+
+ override fun calculateFees(feeHistory: FeeHistory): BigInteger {
+ val baseFeePerGasList = feeHistory.baseFeePerGas.map { BigDecimal(it) }
+ val priorityFeesPerGasList = feeHistory.reward.map { BigDecimal(it[0]) }
+ var gasUsageRatioList = feeHistory.gasUsedRatio
+ var gasUsageRatiosSum = gasUsageRatioList.sumOf { it }
+
+ if (gasUsageRatiosSum.compareTo(BigDecimal.ZERO) == 0) {
+ log.warn(
+ "GasUsedRatio is zero for all l1Blocks={}. Will fallback to Simple Average.",
+ feeHistory.blocksRange().toIntervalString()
+ )
+ // Giving a weight of one will yield the simple average
+ gasUsageRatioList = feeHistory.gasUsedRatio.map { BigDecimal.ONE }
+ gasUsageRatiosSum = BigDecimal(feeHistory.gasUsedRatio.size)
+ }
+
+ val weightedFeesSum = gasUsageRatioList.mapIndexed { index, gasUsedRatio ->
+ val baseFeeFractional = baseFeePerGasList[index].multiply(this.config.baseFeeCoefficient)
+ val priorityFeeFractional = priorityFeesPerGasList[index].multiply(this.config.priorityFeeCoefficient)
+
+ baseFeeFractional.add(priorityFeeFractional).multiply(gasUsedRatio)
+ }.reduce { acc, bigDecimal -> acc.add(bigDecimal) }
+
+ val l2GasPrice = weightedFeesSum
+ .divide(gasUsageRatiosSum, MathContext.DECIMAL128)
+ .setScale(0, java.math.RoundingMode.HALF_UP)
+ .toBigInteger()
+
+ log.debug(
+ "Calculated gasPrice={} wei, l1Blocks={}",
+ l2GasPrice,
+ feeHistory.blocksRange().toIntervalString()
+ )
+ return l2GasPrice
+ }
+}
diff --git a/coordinator/app/src/main/kotlin/net/consensys/zkevm/ethereum/coordination/dynamicgasprice/WMAFeesCalculator.kt b/coordinator/app/src/main/kotlin/net/consensys/zkevm/ethereum/coordination/dynamicgasprice/WMAFeesCalculator.kt
new file mode 100644
index 000000000..724670a0b
--- /dev/null
+++ b/coordinator/app/src/main/kotlin/net/consensys/zkevm/ethereum/coordination/dynamicgasprice/WMAFeesCalculator.kt
@@ -0,0 +1,73 @@
+package net.consensys.zkevm.ethereum.coordination.dynamicgasprice
+
+import net.consensys.linea.FeeHistory
+import net.consensys.linea.toIntervalString
+import org.apache.logging.log4j.LogManager
+import org.apache.logging.log4j.Logger
+import java.math.BigDecimal
+import java.math.BigInteger
+import java.math.MathContext
+
+/**
+ * Weighted Average Fees Calculator
+ * WMA Gasfee = (lastBaseFee * baseFeeCoefficient) +
+ * (sumOf(reward[i] * ratio[i] * (i+1)) / sumOf(ratio[i] * (i+1))) * priorityFeeWmaCoefficient
+ */
+class WMAFeesCalculator(
+ private val config: Config
+) : FeesCalculator {
+ data class Config(
+ val baseFeeCoefficient: BigDecimal,
+ val priorityFeeWmaCoefficient: BigDecimal
+ )
+
+ private val log: Logger = LogManager.getLogger(this::class.java)
+
+ override fun calculateFees(feeHistory: FeeHistory): BigInteger {
+ val lastBlockBaseFeePerGas =
+ BigDecimal(feeHistory.baseFeePerGas[feeHistory.baseFeePerGas.lastIndex])
+ val scaledBaseFee = lastBlockBaseFeePerGas.multiply(this.config.baseFeeCoefficient)
+ val weightedSumOfRewards = feeHistory.reward
+ .mapIndexed { index, rewards ->
+ feeHistory.gasUsedRatio[index]
+ .multiply(BigDecimal(index + 1))
+ .multiply(BigDecimal(rewards[0]))
+ }
+ .reduce { acc, reward -> acc.add(reward) }
+
+ val weightedRatiosSum = feeHistory.gasUsedRatio
+ .mapIndexed { index, gasUsedRatio -> gasUsedRatio.multiply(BigDecimal(index + 1)) }
+ .reduce { acc, bigDecimal -> acc.add(bigDecimal) }
+
+ try {
+ val weightedPriorityFee = when {
+ weightedSumOfRewards.compareTo(BigDecimal.ZERO) == 0 ||
+ weightedRatiosSum.compareTo(BigDecimal.ZERO) == 0 -> BigDecimal.ZERO
+
+ else ->
+ weightedSumOfRewards
+ .divide(weightedRatiosSum, MathContext.DECIMAL128)
+ .multiply(this.config.priorityFeeWmaCoefficient)
+ }
+
+ val calculatedL2GasPrice = scaledBaseFee.add(weightedPriorityFee).toBigInteger()
+
+ log.debug(
+ "Calculated gasPrice={} wei, l1Blocks={}",
+ calculatedL2GasPrice,
+ feeHistory.blocksRange().toIntervalString()
+ )
+ return calculatedL2GasPrice
+ } catch (e: Exception) {
+ log.error(
+ "Error: weightedSumOfRewards={}, weightedRatiosSum={}, feehistory={}, errorMessage={}",
+ weightedSumOfRewards,
+ weightedRatiosSum,
+ feeHistory,
+ e.message,
+ e
+ )
+ throw e
+ }
+ }
+}
diff --git a/coordinator/app/src/main/kotlin/net/consensys/zkevm/ethereum/coordination/dynamicgasprice/WMAGasProvider.kt b/coordinator/app/src/main/kotlin/net/consensys/zkevm/ethereum/coordination/dynamicgasprice/WMAGasProvider.kt
new file mode 100644
index 000000000..cb0edb20e
--- /dev/null
+++ b/coordinator/app/src/main/kotlin/net/consensys/zkevm/ethereum/coordination/dynamicgasprice/WMAGasProvider.kt
@@ -0,0 +1,66 @@
+package net.consensys.zkevm.ethereum.coordination.dynamicgasprice
+
+import org.web3j.tx.gas.ContractEIP1559GasProvider
+import java.math.BigInteger
+
+class WMAGasProvider(
+ private val chainId: Long,
+ private val feesFetcher: FeesFetcher,
+ private val minerTipCalculator: FeesCalculator,
+ private val config: Config
+) :
+ ContractEIP1559GasProvider {
+
+ data class Config(
+ val gasLimit: BigInteger,
+ val maxFeePerGas: BigInteger
+ )
+ private data class Fees(
+ val maxFeePerGas: BigInteger,
+ val maxPriorityFeePerGas: BigInteger
+ )
+
+ private fun getRecentFees(): Fees {
+ return feesFetcher.getL1EthGasPriceData().thenApply {
+ feesData ->
+ val minerTip = minerTipCalculator.calculateFees(feesData)
+ val maxFeePerGas = feesData.baseFeePerGas[feesData.baseFeePerGas.lastIndex - 1]
+ .multiply(BigInteger.TWO).add(minerTip).min(config.maxFeePerGas)
+ Fees(maxFeePerGas, minerTip)
+ }.get()
+ }
+
+ override fun getGasPrice(contractFunc: String?): BigInteger {
+ return getGasPrice()
+ }
+
+ @Deprecated("Deprecated in Java")
+ override fun getGasPrice(): BigInteger {
+ throw NotImplementedError("EIP1559GasProvider only implements EIP1559 specific methods")
+ }
+
+ override fun getGasLimit(contractFunc: String?): BigInteger {
+ return getGasLimit()
+ }
+
+ @Deprecated("Deprecated in Java")
+ override fun getGasLimit(): BigInteger {
+ return config.gasLimit
+ }
+
+ override fun isEIP1559Enabled(): Boolean {
+ return true
+ }
+
+ override fun getChainId(): Long {
+ return chainId
+ }
+
+ override fun getMaxFeePerGas(contractFunc: String?): BigInteger {
+ return getRecentFees().maxFeePerGas
+ }
+
+ override fun getMaxPriorityFeePerGas(contractFunc: String?): BigInteger {
+ return getRecentFees().maxPriorityFeePerGas
+ }
+}
diff --git a/coordinator/app/src/main/kotlin/net/consensys/zkevm/ethereum/coordination/messageanchoring/L1EventQuerierImpl.kt b/coordinator/app/src/main/kotlin/net/consensys/zkevm/ethereum/coordination/messageanchoring/L1EventQuerierImpl.kt
new file mode 100644
index 000000000..45dc1ccd7
--- /dev/null
+++ b/coordinator/app/src/main/kotlin/net/consensys/zkevm/ethereum/coordination/messageanchoring/L1EventQuerierImpl.kt
@@ -0,0 +1,241 @@
+package net.consensys.zkevm.ethereum.coordination.messageanchoring
+
+import io.vertx.core.Vertx
+import net.consensys.linea.async.toSafeFuture
+import net.consensys.linea.contract.ZkEvmV2
+import org.apache.logging.log4j.LogManager
+import org.apache.logging.log4j.Logger
+import org.apache.tuweni.bytes.Bytes32
+import org.web3j.abi.EventEncoder
+import org.web3j.protocol.Web3j
+import org.web3j.protocol.core.DefaultBlockParameter
+import org.web3j.protocol.core.methods.request.EthFilter
+import org.web3j.protocol.core.methods.response.Log
+import tech.pegasys.teku.infrastructure.async.SafeFuture
+import java.math.BigInteger
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
+
+class L1EventQuerierImpl(
+ private val vertx: Vertx,
+ private val config: Config,
+ private val l1Web3jClient: Web3j
+) : L1EventQuerier {
+ companion object {
+ val encodedMessageSentEvent: String = EventEncoder.encode(ZkEvmV2.MESSAGESENT_EVENT)
+
+ fun parseMessageSentEventLogs(log: Log): SendMessageEvent {
+ return SendMessageEvent(Bytes32.wrap(ZkEvmV2.getMessageSentEventFromLog(log)._messageHash))
+ }
+ }
+
+ data class QueryStateParameters(
+ var startingBlock: BigInteger,
+ var finalBlock: BigInteger,
+ var startingEventLogIndex: BigInteger
+ )
+
+ private val startingLogIndexToIncludeAllLogs = BigInteger.valueOf(-1)
+ private val log: Logger = LogManager.getLogger(this::class.java)
+
+ class Config(
+ val pollingInterval: Duration,
+ val maxEventScrapingTime: Duration,
+ val earliestL1Block: BigInteger,
+ val maxMessagesToCollect: UInt,
+ val l1MessageServiceAddress: String,
+ val finalized: String,
+ val blockRangeLoopLimit: UInt
+ )
+
+ override fun getSendMessageEventsForAnchoredMessage(
+ messageHash: MessageHashAnchoredEvent?
+ ): SafeFuture> {
+ return vertx
+ .executeBlocking { promise ->
+ try {
+ val finalBlock = getFinalBlock()
+ val initialQueryStateParameters =
+ if (messageHash != null) {
+ // get startingBlock and index to start ignoring from for first round of data
+ log.debug("Starting with message hash: {}", messageHash.messageHash)
+ getBlockAtEventEmission(finalBlock, messageHash)
+ } else {
+ log.debug("Starting hash is null, using earliest block and latest block")
+ QueryStateParameters(config.earliestL1Block, finalBlock, startingLogIndexToIncludeAllLogs)
+ }
+
+ val collectedEvents = collectEvents(initialQueryStateParameters)
+ log.debug(
+ "Completing with events: ${collectedEvents.count()} with maxMessagesToAnchor:${config.maxMessagesToCollect}"
+ )
+ promise.complete(collectedEvents.take(config.maxMessagesToCollect.toInt()))
+ } catch (th: Throwable) {
+ promise.fail(th)
+ }
+ }
+ .toSafeFuture()
+ }
+
+ private fun collectEvents(queryStateParameters: QueryStateParameters): List {
+ val collectedEvents: MutableList = mutableListOf()
+ var eventCollectionQueryParameters = queryStateParameters
+ val startTimestampMillis = System.currentTimeMillis()
+ var elapsedTimeMillis: Long
+ do {
+ val finalBlock = getFinalBlock()
+
+ // NB! make sure we only use the latest finalized block or within limits
+ eventCollectionQueryParameters.finalBlock = getFinalBlockForQueryingWithinLimits(
+ eventCollectionQueryParameters.startingBlock,
+ finalBlock
+ )
+
+ log.trace("Querying for events with {}", eventCollectionQueryParameters)
+ // get the mapped events and next block number to start from
+ val (newEvents, nextQueryParameters) = getEventsFromLogIndexInRange(eventCollectionQueryParameters)
+ eventCollectionQueryParameters = nextQueryParameters
+ collectedEvents.addAll(newEvents)
+
+ // we may have enough messages, so we could end
+ if (collectedEvents.count().toUInt() >= config.maxMessagesToCollect) {
+ break
+ }
+
+ Thread.sleep(config.pollingInterval.inWholeMilliseconds)
+ elapsedTimeMillis = System.currentTimeMillis() - startTimestampMillis
+ } while (elapsedTimeMillis.milliseconds < config.maxEventScrapingTime)
+ return collectedEvents
+ }
+
+ private fun getFinalBlock(): BigInteger = l1Web3jClient
+ .ethGetBlockByNumber(DefaultBlockParameter.valueOf(config.finalized), false)
+ .send()
+ .block
+ .number
+
+ private fun getEventsFromLogIndexInRange(
+ queryStateParameters: QueryStateParameters
+ ): Pair, QueryStateParameters> {
+ val tempLogs = l1Web3jClient.ethGetLogs(
+ buildEventFilter(
+ queryStateParameters.startingBlock,
+ queryStateParameters.finalBlock
+ )
+ ).send().logs
+
+ log.trace("Getting events with {}", queryStateParameters)
+
+ val newLogs = tempLogs.filter { logResult ->
+ isEventAfterEventOnInitialBlock(
+ logResult.get(),
+ queryStateParameters.startingEventLogIndex,
+ queryStateParameters.startingBlock
+ )
+ }
+
+ return when (true) {
+ newLogs.isNotEmpty() -> {
+ val lastLog = (newLogs.last().get() as Log)
+
+ val newStartingBlock = BigInteger.valueOf(lastLog.blockNumber.toLong())
+ val newStartingIndex = BigInteger.valueOf(lastLog.logIndex.toLong())
+
+ val nextQueryStateParameters = QueryStateParameters(
+ newStartingBlock,
+ queryStateParameters.finalBlock,
+ newStartingIndex
+ )
+
+ val events = newLogs.map { mappingLog -> parseMessageSentEventLogs(mappingLog.get() as Log) }
+
+ return Pair(events, nextQueryStateParameters)
+ }
+ startAndFinalBlockAreSame(queryStateParameters) -> {
+ Pair(
+ listOf(),
+ QueryStateParameters(
+ queryStateParameters.finalBlock,
+ queryStateParameters.finalBlock,
+ queryStateParameters.startingEventLogIndex
+ )
+ )
+ }
+ else -> {
+ Pair(
+ listOf(),
+ QueryStateParameters(
+ queryStateParameters.finalBlock,
+ queryStateParameters.finalBlock,
+ startingLogIndexToIncludeAllLogs
+ )
+ )
+ }
+ }
+ }
+
+ private fun startAndFinalBlockAreSame(queryStateParameters: QueryStateParameters): Boolean {
+ return queryStateParameters.startingBlock == queryStateParameters.finalBlock
+ }
+ private fun isEventAfterEventOnInitialBlock(
+ logResult: Any,
+ logIndex: BigInteger,
+ startingBlock: BigInteger
+ ): Boolean {
+ val eventLog = (logResult as Log)
+ return (eventLog.blockNumber == startingBlock && eventLog.logIndex > logIndex) ||
+ (eventLog.blockNumber > startingBlock)
+ }
+
+ private fun buildEventFilter(startingBlock: BigInteger, finalBlock: BigInteger): EthFilter {
+ val sentMessagesFilter =
+ EthFilter(
+ DefaultBlockParameter.valueOf(startingBlock),
+ DefaultBlockParameter.valueOf(finalBlock),
+ config.l1MessageServiceAddress
+ )
+
+ sentMessagesFilter.addSingleTopic(encodedMessageSentEvent)
+ return sentMessagesFilter
+ }
+
+ private fun getBlockAtEventEmission(
+ finalBlock: BigInteger,
+ messageHash: MessageHashAnchoredEvent
+ ): QueryStateParameters {
+ val messageHashFilter =
+ EthFilter(
+ DefaultBlockParameter.valueOf(config.earliestL1Block),
+ DefaultBlockParameter.valueOf(finalBlock),
+ config.l1MessageServiceAddress
+ )
+
+ messageHashFilter.addSingleTopic(encodedMessageSentEvent)
+ messageHashFilter.addNullTopic()
+ messageHashFilter.addNullTopic()
+ messageHashFilter.addSingleTopic(messageHash.messageHash.toString())
+
+ log.trace("Trying to find the event in range [{} .. {}]", config.earliestL1Block, finalBlock)
+ // get the block where the hash was found
+ val logs = l1Web3jClient.ethGetLogs(messageHashFilter).send().logs
+
+ return if (logs.isNotEmpty()) {
+ val eventLog = logs.first().get() as Log
+ val finalBlockWithinLimits = getFinalBlockForQueryingWithinLimits(eventLog.blockNumber, finalBlock)
+ log.trace("Found event hash at block {}", eventLog.blockNumber)
+ QueryStateParameters(eventLog.blockNumber, finalBlockWithinLimits, eventLog.logIndex)
+ } else {
+ val finalBlockWithinLimits = getFinalBlockForQueryingWithinLimits(config.earliestL1Block, finalBlock)
+ QueryStateParameters(config.earliestL1Block, finalBlockWithinLimits, startingLogIndexToIncludeAllLogs)
+ }
+ }
+
+ private fun getFinalBlockForQueryingWithinLimits(
+ startingBlock: BigInteger,
+ finalBlock: BigInteger
+ ): BigInteger {
+ val loopLimit = BigInteger.valueOf(config.blockRangeLoopLimit.toLong())
+
+ return minOf(startingBlock + loopLimit, finalBlock)
+ }
+}
diff --git a/coordinator/app/src/main/kotlin/net/consensys/zkevm/ethereum/coordination/messageanchoring/L2MessageAnchorerImpl.kt b/coordinator/app/src/main/kotlin/net/consensys/zkevm/ethereum/coordination/messageanchoring/L2MessageAnchorerImpl.kt
new file mode 100644
index 000000000..b9e9174c5
--- /dev/null
+++ b/coordinator/app/src/main/kotlin/net/consensys/zkevm/ethereum/coordination/messageanchoring/L2MessageAnchorerImpl.kt
@@ -0,0 +1,61 @@
+package net.consensys.zkevm.ethereum.coordination.messageanchoring
+
+import io.vertx.core.Vertx
+import net.consensys.linea.async.retryWithInterval
+import net.consensys.linea.contract.L2MessageService
+import org.apache.logging.log4j.LogManager
+import org.apache.logging.log4j.Logger
+import org.apache.tuweni.bytes.Bytes32
+import org.web3j.protocol.Web3j
+import org.web3j.protocol.core.methods.response.EthBlock
+import org.web3j.protocol.core.methods.response.TransactionReceipt
+import tech.pegasys.teku.infrastructure.async.SafeFuture
+import java.math.BigInteger
+import kotlin.time.Duration
+
+class L2MessageAnchorerImpl(
+ private val vertx: Vertx,
+ private val l2Web3j: Web3j,
+ private val l2Client: L2MessageService,
+ private val config: Config
+) : L2MessageAnchorer {
+
+ class Config(
+ val receiptPollingInterval: Duration,
+ val maxReceiptRetries: UInt,
+ val blocksToFinalisation: Long
+ )
+
+ private val log: Logger = LogManager.getLogger(this::class.java)
+
+ override fun anchorMessages(messageHashes: List): SafeFuture {
+ log.debug("Anchoring {}, hashes={}", messageHashes.count(), messageHashes.map { it.toHexString() })
+
+ return SafeFuture.of(
+ l2Client.addL1L2MessageHashes(messageHashes.map { arr -> arr.toArray() })
+ .sendAsync()
+ .thenCompose { txReceipt ->
+ val safeBlock = txReceipt.blockNumber.add(
+ BigInteger.valueOf(config.blocksToFinalisation)
+ )
+ retryWithInterval(
+ config.maxReceiptRetries.toInt(),
+ config.receiptPollingInterval,
+ vertx,
+ transactionIsSafe(
+ safeBlock
+ )
+ ) {
+ SafeFuture.of(l2Web3j.ethGetBlockByNumber({ "latest" }, false).sendAsync())
+ }.whenException {
+ log.warn(it.message, messageHashes)
+ }.thenApply {
+ txReceipt
+ }
+ }
+ )
+ }
+
+ private fun transactionIsSafe(safeBlockNumber: BigInteger) =
+ { result: EthBlock -> result.block.number >= safeBlockNumber }
+}
diff --git a/coordinator/app/src/main/kotlin/net/consensys/zkevm/ethereum/coordination/messageanchoring/L2QuerierImpl.kt b/coordinator/app/src/main/kotlin/net/consensys/zkevm/ethereum/coordination/messageanchoring/L2QuerierImpl.kt
new file mode 100644
index 000000000..0d546853f
--- /dev/null
+++ b/coordinator/app/src/main/kotlin/net/consensys/zkevm/ethereum/coordination/messageanchoring/L2QuerierImpl.kt
@@ -0,0 +1,110 @@
+package net.consensys.zkevm.ethereum.coordination.messageanchoring
+
+import io.vertx.core.Vertx
+import net.consensys.linea.async.toSafeFuture
+import net.consensys.linea.contract.L2MessageService
+import org.apache.logging.log4j.LogManager
+import org.apache.logging.log4j.Logger
+import org.apache.tuweni.bytes.Bytes32
+import org.web3j.abi.EventEncoder
+import org.web3j.protocol.Web3j
+import org.web3j.protocol.core.DefaultBlockParameter
+import org.web3j.protocol.core.methods.response.Log
+import tech.pegasys.teku.infrastructure.async.SafeFuture
+import java.math.BigInteger
+
+class L2QuerierImpl(
+ private val l2Client: Web3j,
+ private val messageService: L2MessageService,
+ private val config: Config,
+ private val vertx: Vertx
+) : L2Querier {
+ private val log: Logger = LogManager.getLogger(this::class.java)
+
+ data class Config(
+ val blocksToFinalizationL2: UInt,
+ val lastHashSearchWindow: UInt,
+ val lastHashSearchMaxBlocksBack: UInt,
+ val contractAddressToListen: String
+ )
+
+ private fun finalizedBlockNumber(): SafeFuture {
+ return SafeFuture.of(
+ l2Client.ethBlockNumber().sendAsync().thenApply {
+ it.blockNumber.minus(BigInteger.valueOf(config.blocksToFinalizationL2.toLong()))
+ }
+ )
+ }
+
+ override fun findLastFinalizedAnchoredEvent(): SafeFuture {
+ return vertx
+ .executeBlocking { promise ->
+ var finalizedBlockNumber = l2Client.ethBlockNumber().send().blockNumber
+ if (finalizedBlockNumber > BigInteger.valueOf(config.blocksToFinalizationL2.toLong())) {
+ finalizedBlockNumber = finalizedBlockNumber.minus(BigInteger.valueOf(config.blocksToFinalizationL2.toLong()))
+ }
+
+ val bigIntSearchWindow = BigInteger.valueOf(config.lastHashSearchWindow.toLong())
+ var startingBlock: BigInteger = BigInteger.valueOf(0)
+ if (finalizedBlockNumber > bigIntSearchWindow) {
+ startingBlock = finalizedBlockNumber.minus(bigIntSearchWindow)
+ }
+ var endingBlock = finalizedBlockNumber
+ val oldestBlockToSearchIn = BigInteger.valueOf(0)
+
+ if (finalizedBlockNumber > BigInteger.valueOf(config.lastHashSearchMaxBlocksBack.toLong())) {
+ finalizedBlockNumber.minus(BigInteger.valueOf(config.lastHashSearchMaxBlocksBack.toLong()))
+ }
+
+ log.debug(
+ "Starting to search for events with startingBlock=$startingBlock , " +
+ "endingBlock=$endingBlock, oldestBlockToSearchIn=$oldestBlockToSearchIn"
+ )
+
+ messageService.setDefaultBlockParameter(DefaultBlockParameter.valueOf(finalizedBlockNumber))
+
+ while (startingBlock >= oldestBlockToSearchIn) {
+ try {
+ val messageHashFilter =
+ org.web3j.protocol.core.methods.request.EthFilter(
+ DefaultBlockParameter.valueOf(startingBlock),
+ DefaultBlockParameter.valueOf(endingBlock),
+ messageService.contractAddress
+ )
+
+ messageHashFilter.addSingleTopic(EventEncoder.encode(L2MessageService.L1L2MESSAGEHASHESADDEDTOINBOX_EVENT))
+
+ val logs = l2Client.ethGetLogs(messageHashFilter).send().logs
+ if (logs.isNotEmpty()) {
+ val lastLog = logs.last().get() as Log
+ val messageHash =
+ L2MessageService.getL1L2MessageHashesAddedToInboxEventFromLog(lastLog).messageHashes.last()
+ log.debug("Returning found hash={}", Bytes32.wrap(messageHash))
+ promise.complete(MessageHashAnchoredEvent(Bytes32.wrap(messageHash)))
+ break
+ } else {
+ endingBlock = startingBlock
+ startingBlock = startingBlock.minus(bigIntSearchWindow)
+ }
+ } catch (th: Throwable) {
+ promise.fail(th)
+ break
+ }
+ }
+ if (promise.tryComplete(null)) {
+ log.debug("No hashes found")
+ }
+ }
+ .toSafeFuture()
+ }
+
+ override fun getMessageHashStatus(messageHash: Bytes32): SafeFuture {
+ return SafeFuture.of(
+ finalizedBlockNumber().thenApply {
+ messageService.setDefaultBlockParameter(DefaultBlockParameter.valueOf(it))
+ }.thenCompose {
+ messageService.inboxL1L2MessageStatus(messageHash.toArray()).sendAsync()
+ }
+ )
+ }
+}
diff --git a/coordinator/app/src/main/kotlin/net/consensys/zkevm/ethereum/coordination/messageanchoring/MessageAnchoringService.kt b/coordinator/app/src/main/kotlin/net/consensys/zkevm/ethereum/coordination/messageanchoring/MessageAnchoringService.kt
new file mode 100644
index 000000000..547bcb837
--- /dev/null
+++ b/coordinator/app/src/main/kotlin/net/consensys/zkevm/ethereum/coordination/messageanchoring/MessageAnchoringService.kt
@@ -0,0 +1,99 @@
+package net.consensys.zkevm.ethereum.coordination.messageanchoring
+
+import io.vertx.core.TimeoutStream
+import io.vertx.core.Vertx
+import net.consensys.linea.contract.AsyncFriendlyTransactionManager
+import net.consensys.linea.contract.L2MessageService
+import org.apache.logging.log4j.LogManager
+import org.apache.logging.log4j.Logger
+import tech.pegasys.teku.infrastructure.async.SafeFuture
+import java.math.BigInteger
+import kotlin.time.Duration
+
+class MessageAnchoringService(
+ private val config: Config,
+ private val vertx: Vertx,
+ private val l1EventQuerier: L1EventQuerier,
+ private val l2MessageAnchorer: L2MessageAnchorer,
+ private val l2Querier: L2Querier,
+ l2MessageService: L2MessageService,
+ private val transactionManager: AsyncFriendlyTransactionManager
+) {
+ class Config(
+ val pollingInterval: Duration,
+ val maxMessagesToAnchor: UInt
+ )
+
+ private val log: Logger = LogManager.getLogger(this::class.java)
+
+ @Volatile
+ private lateinit var monitorStream: TimeoutStream
+ private val inboxStatusUnknown: BigInteger = l2MessageService.INBOX_STATUS_UNKNOWN().send()
+
+ private fun tick(): SafeFuture {
+ return l2Querier
+ .findLastFinalizedAnchoredEvent()
+ .thenCompose(l1EventQuerier::getSendMessageEventsForAnchoredMessage)
+ .thenCompose { sentMessages ->
+ SafeFuture.collectAll(
+ sentMessages
+ .map { sendMessageEvent ->
+ l2Querier.getMessageHashStatus(sendMessageEvent.messageHash).thenApply { status
+ ->
+ sendMessageEvent to status
+ }
+ }
+ .stream()
+ )
+ }
+ .thenApply { eventAndStatusPairs ->
+ eventAndStatusPairs
+ .filter { eventAndStatus -> eventAndStatus.second == inboxStatusUnknown }
+ .map { it.first }
+ }
+ .thenCompose { eventsToAnchor ->
+ if (eventsToAnchor.isNotEmpty()) {
+ log.debug("Found {} un-anchored events", eventsToAnchor.count())
+ transactionManager.resetNonce().thenCompose {
+ l2MessageAnchorer.anchorMessages(
+ eventsToAnchor.take(config.maxMessagesToAnchor.toInt())
+ .map { transactionReceipt -> transactionReceipt.messageHash }
+ )
+ }.thenApply { transactionReceipt ->
+ log.info("Message anchoring transactionHash=${transactionReceipt.transactionHash}")
+ }
+ } else {
+ log.debug("Skipping anchoring as there are no hashes")
+ SafeFuture.completedFuture(Unit)
+ }
+ }
+ }
+
+ fun start(): SafeFuture {
+ monitorStream =
+ vertx.periodicStream(config.pollingInterval.inWholeMilliseconds).handler {
+ try {
+ monitorStream.pause()
+ tick()
+ .whenComplete { _, error ->
+ error?.let {
+ log.error("Failed to anchor messages: errorMessage={}", error.message, error)
+ }
+ monitorStream.resume()
+ }
+ } catch (th: Throwable) {
+ log.error("Failed to trigger message anchoring: errorMessage={}", th.message, th)
+ monitorStream.resume()
+ }
+ }
+ return SafeFuture.completedFuture(Unit)
+ }
+
+ fun stop(): SafeFuture {
+ if (this::monitorStream.isInitialized) {
+ return SafeFuture.completedFuture(monitorStream.cancel())
+ } else {
+ throw IllegalStateException("Message Anchoring Service hasn't been started to stop it!")
+ }
+ }
+}
diff --git a/coordinator/app/src/test/kotlin/net/consensys/zkevm/coordinator/app/CoordinatorConfigTest.kt b/coordinator/app/src/test/kotlin/net/consensys/zkevm/coordinator/app/CoordinatorConfigTest.kt
new file mode 100644
index 000000000..af0e3cb1e
--- /dev/null
+++ b/coordinator/app/src/test/kotlin/net/consensys/zkevm/coordinator/app/CoordinatorConfigTest.kt
@@ -0,0 +1,307 @@
+package net.consensys.zkevm.coordinator.app
+
+import com.sksamuel.hoplite.Masked
+import net.consensys.linea.traces.TracingModule
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+import java.io.File
+import java.io.PrintWriter
+import java.math.BigInteger
+import java.net.URL
+import java.nio.charset.Charset
+import java.nio.file.Path
+import java.time.Duration
+
+class CoordinatorConfigTest {
+ val errorWriter = PrintWriter(System.err, true, Charset.defaultCharset())
+
+ companion object {
+
+ val apiConfig = ApiConfig(9545U)
+
+ val conflationConfig = ConflationConfig(
+ 1,
+ Duration.parse("PT6S"),
+ Duration.parse("PT3S"),
+ Duration.parse("PT2S"),
+ 129072,
+ 40,
+ 1699,
+ null,
+ null,
+ mapOf(
+ TracingModule.ADD to 262144U,
+ TracingModule.BIN to 262144U,
+ TracingModule.BIN_RT to 262144U,
+ TracingModule.EC_DATA to 4096U,
+ TracingModule.EXT to 16384U,
+ TracingModule.HUB to 2097152U,
+ TracingModule.INSTRUCTION_DECODER to 512U,
+ TracingModule.MMIO to 1048576U,
+ TracingModule.MMU to 524288U,
+ TracingModule.MMU_ID to 256U,
+ TracingModule.MOD to 131072U,
+ TracingModule.MUL to 65536U,
+ TracingModule.MXP to 524288U,
+ TracingModule.PHONEY_RLP to 65536U,
+ TracingModule.PUB_HASH to 32768U,
+ TracingModule.PUB_HASH_INFO to 8192U,
+ TracingModule.PUB_LOG to 16384U,
+ TracingModule.PUB_LOG_INFO to 16384U,
+ TracingModule.RLP to 128U,
+ TracingModule.ROM to 1048576U,
+ TracingModule.SHF to 65536U,
+ TracingModule.SHF_RT to 262144U,
+ TracingModule.TX_RLP to 65536U,
+ TracingModule.WCP to 262144U,
+ TracingModule.BLOCK_TX to 200U,
+ TracingModule.BLOCK_L2L1LOGS to 16U,
+ TracingModule.BLOCK_KECCAK to 8192U,
+ TracingModule.PRECOMPILE_ECRECOVER to 10000U,
+ TracingModule.PRECOMPILE_SHA2 to 10000U,
+ TracingModule.PRECOMPILE_RIPEMD to 10000U,
+ TracingModule.PRECOMPILE_IDENTITY to 10000U,
+ TracingModule.PRECOMPILE_MODEXP to 10000U,
+ TracingModule.PRECOMPILE_ECADD to 10000U,
+ TracingModule.PRECOMPILE_ECMUL to 10000U,
+ TracingModule.PRECOMPILE_ECPAIRING to 10000U,
+ TracingModule.PRECOMPILE_BLAKE2F to 512U
+ )
+ )
+
+ val zkGethTracesConfig = ZkGethTraces(
+ URL("http://traces-node:8545"),
+ Duration.parse("PT1S")
+ )
+
+ val sequencerConfig = SequencerConfig(
+ URL("http://sequencer:8545")
+ )
+
+ val proverConfig = ProverConfig(
+ version = "0.2.0",
+ fsInputDirectory = Path.of("/data/prover/request"),
+ fsOutputDirectory = Path.of("/data/prover/response"),
+ fsPollingInterval = Duration.parse("PT20S"),
+ fsInprogessProvingSuffixPattern = "\\.inprogress\\.prover.*",
+ timeout = Duration.parse("PT10M")
+ )
+
+ val tracesConfig = TracesConfig(
+ "0.2.0",
+ TracesConfig.FunctionalityEndpoint(
+ listOf(
+ URL("http://traces-api:8080/")
+ ),
+ 20U,
+ 2U,
+ Duration.parse("PT1S")
+ ),
+ TracesConfig.FunctionalityEndpoint(
+ listOf(
+ URL("http://traces-api:8080/")
+ ),
+ 2U,
+ 2U,
+ Duration.parse("PT1S")
+ ),
+ TracesConfig.FileManager(
+ "json.gz",
+ Path.of("/data/traces/raw"),
+ Path.of("/data/traces/raw-non-canonical"),
+ true,
+ Duration.parse("PT1S"),
+ Duration.parse("PT30S")
+ )
+ )
+
+ val stateManagerConfig = StateManagerClientConfig(
+ "1.2.0",
+ listOf(
+ URL("http://shomei:8888/")
+ ),
+ 3U
+ )
+
+ val batchSubmissionConfig = BatchSubmissionConfig(
+ 10,
+ Duration.parse("PT1S")
+ )
+
+ val databaseConfig = DatabaseConfig(
+ "postgres",
+ 5432,
+ "postgres",
+ Masked("postgres"),
+ "linea_coordinator",
+ 10,
+ 10,
+ 10
+ )
+
+ val l1Config = L1Config(
+ "0xC737F2334651ea85A72D8DA9d933c821A8377F9f",
+ URL("http://l1-validator:8545"),
+ Duration.parse("PT6S"),
+ Duration.parse("PT6S"),
+ 2U,
+ BigInteger.valueOf(10000000),
+ 10,
+ 15.0,
+ BigInteger.valueOf(100000000000),
+ BigInteger.ZERO,
+ Duration.parse("PT6S"),
+ Duration.parse("PT02M"),
+ 1000U,
+ "latest",
+ 5U
+ )
+
+ val l2Config = L2Config(
+ "0xe537D669CA013d86EBeF1D64e40fC74CADC91987",
+ BigInteger.valueOf(10000000),
+ BigInteger.valueOf(100000000000),
+ 4U,
+ 15.0,
+ 2U,
+ 25U,
+ 1000U,
+ Duration.parse("PT01S"),
+ 120U
+ )
+
+ val l1SignerConfig = SignerConfig(
+ SignerConfig.Type.Web3j,
+ Web3SignerConfig(
+ "http://127.0.0.1:9000",
+ 10U,
+ true,
+ "0x3c753c0c9db5ce651144dbba3da47476ddede5b98607272de97a347d72c2eac2be3f6aea33598f297a0cd0f12cf08fe0" +
+ "e5198531393ea726a36efd07a7872382"
+ ),
+ Web3jConfig(Masked("0x202454d1b4e72c41ebf58150030f649648d3cf5590297fb6718e27039ed9c86d"))
+ )
+
+ val l1SignerWeb3Config = SignerConfig(
+ SignerConfig.Type.Web3Signer,
+ Web3SignerConfig(
+ "http://127.0.0.1:9000",
+ 10U,
+ true,
+ "0x3c753c0c9db5ce651144dbba3da47476ddede5b98607272de97a347d72c2eac2be3f6aea33598f297a0cd0f12cf08fe0" +
+ "e5198531393ea726a36efd07a7872382"
+ ),
+ Web3jConfig(Masked("0x202454d1b4e72c41ebf58150030f649648d3cf5590297fb6718e27039ed9c86d"))
+ )
+
+ val l2SignerConfig = SignerConfig(
+ SignerConfig.Type.Web3j,
+ Web3SignerConfig(
+ "http://127.0.0.1:9000",
+ 10U,
+ true,
+ "0cdc73b5a30ecb3eb2028fdd2d5ab423221763fbda7127e38d664b033455e30e7c6833bff6b99197de4d2069de30f6aaa9" +
+ "0626986b737b7e74e635f4f7cedfbf"
+ ),
+ Web3jConfig(Masked("0x1dd171cec7e2995408b5513004e8207fe88d6820aeff0d82463b3e41df251aae"))
+ )
+
+ val l2Web3SignerConfig = SignerConfig(
+ SignerConfig.Type.Web3Signer,
+ Web3SignerConfig(
+ "http://127.0.0.1:9000",
+ 10U,
+ true,
+ "0cdc73b5a30ecb3eb2028fdd2d5ab423221763fbda7127e38d664b033455e30e7c6833bff6b99197de4d2069de30f6aaa9" +
+ "0626986b737b7e74e635f4f7cedfbf"
+ ),
+ Web3jConfig(Masked("0x1dd171cec7e2995408b5513004e8207fe88d6820aeff0d82463b3e41df251aae"))
+ )
+
+ val messageAnchoringServiceConfig = MessageAnchoringServiceConfig(
+ Duration.parse("PT48S"),
+ 100U
+ )
+
+ val dynamicGasPriceServiceConfig = DynamicGasPriceServiceConfig(
+ Duration.parse("PT12S"),
+ 50,
+ 15.0,
+ 0.1.toBigDecimal(),
+ 1.0.toBigDecimal(),
+ BigInteger.valueOf(10_000_000_000),
+ listOf(
+ URL("http://sequencer:8545/"),
+ URL("http://traces-node:8545/"),
+ URL("http://l2-node:8545/")
+ )
+ )
+
+ val coordinatorConfig = CoordinatorConfig(
+ sequencerConfig,
+ zkGethTracesConfig,
+ proverConfig,
+ tracesConfig,
+ l1Config,
+ l2Config,
+ l1SignerConfig,
+ batchSubmissionConfig,
+ databaseConfig,
+ stateManagerConfig,
+ conflationConfig,
+ apiConfig,
+ l2SignerConfig,
+ messageAnchoringServiceConfig,
+ dynamicGasPriceServiceConfig
+ )
+ }
+
+ @Test
+ fun parsesValidConfig() {
+ val tracesLimitsConfigs =
+ CoordinatorAppCli.loadConfigs(
+ listOf(File("../../config/common/traces-limits-v1.toml")),
+ errorWriter
+ )
+ val configs = CoordinatorAppCli.loadConfigs(
+ listOf(File("../../config/coordinator/coordinator-docker.config.toml")),
+ errorWriter
+ )
+ ?.let { config: CoordinatorConfig ->
+ config.copy(
+ conflation = config.conflation.copy(_tracesLimits = tracesLimitsConfigs?.tracesLimits)
+ )
+ }
+
+ assertEquals(coordinatorConfig, configs)
+ }
+
+ @Test
+ fun parsesValidWeb3SignerConfigOverride() {
+ val tracesLimitsConfigs =
+ CoordinatorAppCli.loadConfigs(
+ listOf(File("../../config/common/traces-limits-v1.toml")),
+ errorWriter
+ )
+ val configs =
+ CoordinatorAppCli.loadConfigs(
+ listOf(
+ File("../../config/coordinator/coordinator-docker.config.toml"),
+ File("../../config/coordinator/coordinator-docker-web3signer-override.config.toml")
+ ),
+ errorWriter
+ )?.let {
+ it.copy(
+ conflation = it.conflation.copy(_tracesLimits = tracesLimitsConfigs?.tracesLimits)
+ )
+ }
+
+ val expectedConfig =
+ coordinatorConfig.copy(
+ l1Signer = l1SignerWeb3Config,
+ l2Signer = l2Web3SignerConfig
+ )
+
+ assertEquals(expectedConfig, configs)
+ }
+}
diff --git a/coordinator/app/src/test/kotlin/net/consensys/zkevm/coordinator/app/L1BasedLastFinalizedBlockProviderTest.kt b/coordinator/app/src/test/kotlin/net/consensys/zkevm/coordinator/app/L1BasedLastFinalizedBlockProviderTest.kt
new file mode 100644
index 000000000..b90ae09c5
--- /dev/null
+++ b/coordinator/app/src/test/kotlin/net/consensys/zkevm/coordinator/app/L1BasedLastFinalizedBlockProviderTest.kt
@@ -0,0 +1,54 @@
+package net.consensys.zkevm.coordinator.app
+
+import io.vertx.core.Vertx
+import net.consensys.linea.contract.ZkEvmV2
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+import org.web3j.protocol.core.RemoteFunctionCall
+import java.math.BigInteger
+import java.util.concurrent.CompletableFuture
+import kotlin.time.Duration.Companion.milliseconds
+
+class L1BasedLastFinalizedBlockProviderTest {
+ private lateinit var zkEvmSmartContractWeb3jClient: ZkEvmV2
+
+ @BeforeEach
+ fun beforeEach() {
+ zkEvmSmartContractWeb3jClient = mock()
+ }
+
+ @Test
+ fun `shall wait number of blocks before returning for consistency`() {
+ val replies = listOf(
+ mockRemoteFnCallWithBlockNumber(100),
+ mockRemoteFnCallWithBlockNumber(100),
+ mockRemoteFnCallWithBlockNumber(101),
+ mockRemoteFnCallWithBlockNumber(101),
+ mockRemoteFnCallWithBlockNumber(101),
+ mockRemoteFnCallWithBlockNumber(101)
+ )
+ whenever(zkEvmSmartContractWeb3jClient.currentL2BlockNumber())
+ .thenReturn(replies[0], *replies.subList(1, replies.size).toTypedArray())
+
+ val resumerCalculator = L1BasedLastFinalizedBlockProvider(
+ Vertx.vertx(),
+ zkEvmSmartContractWeb3jClient,
+ consistentNumberOfBlocksOnL1 = 3u,
+ numberOfRetries = 50u,
+ pollingInterval = 10.milliseconds
+ )
+
+ assertThat(resumerCalculator.getLastFinalizedBlock().get()).isEqualTo(101.toULong())
+ }
+
+ private inline fun mockRemoteFnCallWithBlockNumber(blockNumber: Long): RemoteFunctionCall {
+ return mock>() {
+ on { send() } doReturn (BigInteger.valueOf(blockNumber))
+ on { sendAsync() } doReturn (CompletableFuture.completedFuture(BigInteger.valueOf(blockNumber)))
+ }
+ }
+}
diff --git a/coordinator/app/src/test/kotlin/net/consensys/zkevm/coordinator/blockcreation/BlockCreationMonitorTest.kt b/coordinator/app/src/test/kotlin/net/consensys/zkevm/coordinator/blockcreation/BlockCreationMonitorTest.kt
new file mode 100644
index 000000000..d29e1858c
--- /dev/null
+++ b/coordinator/app/src/test/kotlin/net/consensys/zkevm/coordinator/blockcreation/BlockCreationMonitorTest.kt
@@ -0,0 +1,345 @@
+package net.consensys.zkevm.coordinator.blockcreation
+
+import io.vertx.core.Vertx
+import io.vertx.junit5.VertxExtension
+import net.consensys.linea.async.get
+import net.consensys.zkevm.ethereum.coordination.blockcreation.BlockCreated
+import net.consensys.zkevm.ethereum.coordination.blockcreation.BlockCreationListener
+import org.apache.logging.log4j.Logger
+import org.apache.tuweni.bytes.Bytes
+import org.apache.tuweni.bytes.Bytes32
+import org.apache.tuweni.units.bigints.UInt256
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.AfterEach
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.inOrder
+import org.mockito.kotlin.any
+import org.mockito.kotlin.atLeast
+import org.mockito.kotlin.atMost
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+import tech.pegasys.teku.ethereum.executionclient.schema.ExecutionPayloadV1
+import tech.pegasys.teku.infrastructure.async.SafeFuture
+import tech.pegasys.teku.infrastructure.bytes.Bytes20
+import tech.pegasys.teku.infrastructure.unsigned.UInt64
+import java.math.BigInteger
+import java.util.concurrent.Executors
+import java.util.concurrent.TimeUnit
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
+
+@ExtendWith(VertxExtension::class)
+class BlockCreationMonitorTest {
+ private val parentHash =
+ Bytes32.fromHexString("0x1000000000000000000000000000000000000000000000000000000000000000")
+ private val startingBlockNumberInclusive: Long = 100
+ private lateinit var log: Logger
+ private lateinit var web3jClient: ExtendedWeb3J
+ private lateinit var blockCreationListener: BlockCreationListener
+ private var config: BlockCreationMonitor.Config =
+ BlockCreationMonitor.Config(
+ pollingInterval = 100.milliseconds,
+ blocksToFinalization = 2L
+ )
+ private val executor = Executors.newSingleThreadScheduledExecutor()
+ private lateinit var monitor: BlockCreationMonitor
+
+ @BeforeEach
+ fun beforeEach(vertx: Vertx) {
+ web3jClient = mock()
+ blockCreationListener =
+ mock { on { acceptBlock(any()) } doReturn SafeFuture.completedFuture(Unit) }
+
+ log = mock()
+ monitor =
+ BlockCreationMonitor(
+ vertx,
+ web3jClient,
+ startingBlockNumberInclusive,
+ parentHash,
+ blockCreationListener,
+ config
+ )
+ }
+
+ @AfterEach
+ fun afterEach(vertx: Vertx) {
+ monitor.stop()
+ vertx.close().get()
+ }
+
+ // Tried to Test the "Vertx way". Could not make it work, to Will us classi thread sleep :(
+ // @Test
+ // fun `notifies listener after block is finalized`(testContext: VertxTestContext) {
+ // val payload = executionPayloadV1(blockNumber= startingBlockNumberInclusive)
+ // val headBlockNumber = startingBlockNumberInclusive + config.blocksToFinalization
+ // whenever(web3jClient.ethBlockNumber())
+ // .thenReturn(SafeFuture.completedFuture(BigInteger.valueOf(headBlockNumber)))
+ // whenever(web3jClient.ethGetExecutionPayloadByNumber(any()))
+ // .thenReturn(SafeFuture.completedFuture(payload))
+ // val listenerCalled = testContext.checkpoint(1)
+ // whenever(blockCreationListener.acceptBlock(any())).then {
+ // listenerCalled.flag()
+ // SafeFuture.completedFuture(Unit)
+ // }
+ //
+ // monitor.start()
+ // .thenAccept {
+ // testContext.verify {
+ // verify(web3jClient).ethGetExecutionPayloadByNumber(eq(startingBlockNumberInclusive))
+ // verify(blockCreationListener, never()).acceptBlock(BlockCreated(payload))
+ // assertThat(monitor.nexBlockNumberToFetch()).isEqualTo(startingBlockNumberInclusive +
+ // 1)
+ // // testContext.completeNow()
+ // }
+ // }
+ // .whenException(testContext::failNow)
+ // }
+
+ fun waitTicks(numberOfTicks: Int, function: () -> Unit) {
+ Thread.sleep(config.pollingInterval.inWholeMilliseconds * numberOfTicks)
+ function()
+ }
+
+ @Test
+ fun `notifies listener after block is finalized sync`() {
+ val payload =
+ executionPayloadV1(blockNumber = startingBlockNumberInclusive, parentHash = parentHash)
+ val payload2 =
+ executionPayloadV1(
+ blockNumber = startingBlockNumberInclusive + 1,
+ parentHash = payload.blockHash
+ )
+ val headBlockNumber = startingBlockNumberInclusive + config.blocksToFinalization
+ whenever(web3jClient.ethBlockNumber())
+ .thenReturn(SafeFuture.completedFuture(BigInteger.valueOf(headBlockNumber)))
+ .thenReturn(SafeFuture.completedFuture(BigInteger.valueOf(headBlockNumber + 1)))
+ whenever(web3jClient.ethGetExecutionPayloadByNumber(any()))
+ .thenReturn(SafeFuture.completedFuture(payload))
+ .thenReturn(SafeFuture.completedFuture(payload2))
+ whenever(blockCreationListener.acceptBlock(any())).thenReturn(SafeFuture.completedFuture(Unit))
+
+ monitor.start().get()
+
+ waitTicks(3) {
+ verify(web3jClient).ethGetExecutionPayloadByNumber(eq(startingBlockNumberInclusive))
+ verify(web3jClient).ethGetExecutionPayloadByNumber(eq(startingBlockNumberInclusive + 1))
+ verify(blockCreationListener).acceptBlock(BlockCreated(payload))
+ verify(blockCreationListener).acceptBlock(BlockCreated(payload2))
+ assertThat(monitor.nexBlockNumberToFetch).isEqualTo(startingBlockNumberInclusive + 2)
+ }
+ }
+
+ @Test
+ fun `does not notify listener when block is not safely finalized`() {
+ val payload =
+ executionPayloadV1(blockNumber = startingBlockNumberInclusive, parentHash = parentHash)
+ val headBlockNumber = startingBlockNumberInclusive + config.blocksToFinalization - 1
+ whenever(web3jClient.ethBlockNumber())
+ .thenReturn(SafeFuture.completedFuture(BigInteger.valueOf(headBlockNumber)))
+ whenever(web3jClient.ethGetExecutionPayloadByNumber(any()))
+ .thenReturn(SafeFuture.completedFuture(payload))
+ whenever(blockCreationListener.acceptBlock(any())).thenReturn(SafeFuture.completedFuture(Unit))
+
+ monitor.start().get()
+
+ waitTicks(2) {
+ verify(web3jClient, never()).ethGetExecutionPayloadByNumber(any())
+ verify(blockCreationListener, never()).acceptBlock(BlockCreated(payload))
+ assertThat(monitor.nexBlockNumberToFetch).isEqualTo(startingBlockNumberInclusive)
+ }
+ }
+
+ @Test
+ fun `when listener throws retries on the next tick and moves on`() {
+ val payload =
+ executionPayloadV1(blockNumber = startingBlockNumberInclusive, parentHash = parentHash)
+ val headBlockNumber = startingBlockNumberInclusive + config.blocksToFinalization + 1
+ whenever(web3jClient.ethBlockNumber())
+ .thenReturn(SafeFuture.completedFuture(BigInteger.valueOf(headBlockNumber)))
+ whenever(web3jClient.ethGetExecutionPayloadByNumber(any()))
+ .thenReturn(SafeFuture.completedFuture(payload))
+
+ whenever(blockCreationListener.acceptBlock(any()))
+ .thenReturn(SafeFuture.failedFuture(Exception("Notification 1 Error")))
+ .thenThrow(RuntimeException("Notification 2 Error"))
+ .thenReturn(SafeFuture.failedFuture(Exception("Notification 3 Error")))
+ .thenReturn(SafeFuture.completedFuture(Unit))
+
+ monitor.start().get()
+
+ waitTicks(4) {
+ verify(blockCreationListener, atLeast(4)).acceptBlock(BlockCreated(payload))
+ assertThat(monitor.nexBlockNumberToFetch).isEqualTo(startingBlockNumberInclusive + 1)
+ }
+ }
+
+ @Test
+ fun `is resilient to connection failures`() {
+ val payload =
+ executionPayloadV1(blockNumber = startingBlockNumberInclusive, parentHash = parentHash)
+ val headBlockNumber = startingBlockNumberInclusive + config.blocksToFinalization
+ whenever(web3jClient.ethBlockNumber())
+ .thenReturn(SafeFuture.failedFuture(Exception("ethBlockNumber Error 1")))
+ .thenReturn(SafeFuture.completedFuture(BigInteger.valueOf(headBlockNumber)))
+ whenever(web3jClient.ethGetExecutionPayloadByNumber(any()))
+ .thenReturn(SafeFuture.failedFuture(Exception("ethGetExecutionPayloadByNumber Error 1")))
+ .thenReturn(SafeFuture.completedFuture(payload))
+ whenever(blockCreationListener.acceptBlock(any())).thenReturn(SafeFuture.completedFuture(Unit))
+
+ monitor.start().get()
+
+ waitTicks(6) {
+ verify(blockCreationListener, times(1)).acceptBlock(BlockCreated(payload))
+ assertThat(monitor.nexBlockNumberToFetch).isEqualTo(startingBlockNumberInclusive + 1)
+ }
+ }
+
+ @Test
+ fun `should stop when reorg is detected above blocksToFinalization limit - manual intervention necessary`() {
+ val payload =
+ executionPayloadV1(blockNumber = startingBlockNumberInclusive, parentHash = parentHash)
+ val payload2 =
+ executionPayloadV1(
+ blockNumber = startingBlockNumberInclusive + 1,
+ parentHash = Bytes32.random()
+ )
+ val headBlockNumber = startingBlockNumberInclusive + config.blocksToFinalization
+ whenever(web3jClient.ethBlockNumber())
+ .thenReturn(SafeFuture.completedFuture(BigInteger.valueOf(headBlockNumber)))
+ .thenReturn(SafeFuture.completedFuture(BigInteger.valueOf(headBlockNumber + 1)))
+ whenever(web3jClient.ethGetExecutionPayloadByNumber(any()))
+ .thenReturn(SafeFuture.completedFuture(payload))
+ .thenReturn(SafeFuture.completedFuture(payload2))
+ whenever(blockCreationListener.acceptBlock(any())).thenReturn(SafeFuture.completedFuture(Unit))
+
+ monitor.start().get()
+
+ waitTicks(5) {
+ verify(blockCreationListener, times(1)).acceptBlock(BlockCreated(payload))
+ verify(blockCreationListener, never()).acceptBlock(BlockCreated(payload2))
+ assertThat(monitor.nexBlockNumberToFetch).isEqualTo(startingBlockNumberInclusive + 1)
+ }
+ }
+
+ private fun delay(delay: Duration, action: () -> SafeFuture): SafeFuture {
+ val future = SafeFuture()
+ executor.schedule(
+ { action().propagateTo(future) },
+ delay.inWholeMilliseconds,
+ TimeUnit.MILLISECONDS
+ )
+ return future
+ }
+
+ @Test
+ fun `should poll in order when response takes longer that polling interval`() {
+ val payload =
+ executionPayloadV1(blockNumber = startingBlockNumberInclusive, parentHash = parentHash)
+ val payload2 =
+ executionPayloadV1(
+ blockNumber = startingBlockNumberInclusive + 1,
+ parentHash = payload.blockHash
+ )
+ val headBlockNumber = startingBlockNumberInclusive + config.blocksToFinalization
+
+ whenever(web3jClient.ethBlockNumber())
+ .then {
+ delay(config.pollingInterval.times(2)) {
+ SafeFuture.completedFuture(BigInteger.valueOf(headBlockNumber))
+ }
+ }
+ .thenReturn(SafeFuture.completedFuture(BigInteger.valueOf(headBlockNumber + 1)))
+ whenever(web3jClient.ethGetExecutionPayloadByNumber(any()))
+ .then { delay(config.pollingInterval.times(2)) { SafeFuture.completedFuture(payload) } }
+ .thenReturn(SafeFuture.completedFuture(payload2))
+ whenever(blockCreationListener.acceptBlock(any())).thenReturn(SafeFuture.completedFuture(Unit))
+
+ val inOrder = inOrder(blockCreationListener)
+
+ monitor.start().get()
+
+ waitTicks(20) {
+ inOrder.verify(blockCreationListener).acceptBlock(BlockCreated(payload))
+ inOrder.verify(blockCreationListener).acceptBlock(BlockCreated(payload2))
+ assertThat(monitor.nexBlockNumberToFetch).isEqualTo(startingBlockNumberInclusive + 2)
+ }
+ }
+
+ @Test
+ fun `start allow 2nd call when already started`() {
+ monitor.start().get()
+ monitor.start().get()
+ }
+
+ @Test
+ fun `stop should be idempotent`() {
+ val payload =
+ executionPayloadV1(blockNumber = startingBlockNumberInclusive, parentHash = parentHash)
+ val payload2 =
+ executionPayloadV1(
+ blockNumber = startingBlockNumberInclusive + 1,
+ parentHash = payload.blockHash
+ )
+ val headBlockNumber = startingBlockNumberInclusive + config.blocksToFinalization
+ whenever(web3jClient.ethBlockNumber())
+ .thenReturn(SafeFuture.completedFuture(BigInteger.valueOf(headBlockNumber)))
+ .then {
+ delay(config.pollingInterval.times(30)) {
+ SafeFuture.completedFuture(BigInteger.valueOf(headBlockNumber + 1))
+ }
+ }
+ whenever(web3jClient.ethGetExecutionPayloadByNumber(any()))
+ .thenReturn(SafeFuture.completedFuture(payload))
+ .then { delay(config.pollingInterval.times(30)) { SafeFuture.completedFuture(payload2) } }
+ whenever(blockCreationListener.acceptBlock(any())).thenReturn(SafeFuture.completedFuture(Unit))
+
+ monitor.start().get()
+ waitTicks(1) { verify(blockCreationListener, times(1)).acceptBlock(any()) }
+ monitor.stop().get()
+ monitor.stop().get()
+
+ waitTicks(2) { verify(blockCreationListener, atMost(1)).acceptBlock(any()) }
+ }
+
+ private fun executionPayloadV1(
+ blockNumber: Long = 0,
+ parentHash: Bytes32 = Bytes32.random(),
+ feeRecipient: Bytes20 = Bytes20(Bytes.random(20)),
+ stateRoot: Bytes32 = Bytes32.random(),
+ receiptsRoot: Bytes32 = Bytes32.random(),
+ logsBloom: Bytes = Bytes32.random(),
+ prevRandao: Bytes32 = Bytes32.random(),
+ gasLimit: UInt64 = UInt64.valueOf(0),
+ gasUsed: UInt64 = UInt64.valueOf(0),
+ timestamp: UInt64 = UInt64.valueOf(0),
+ extraData: Bytes = Bytes32.random(),
+ baseFeePerGas: UInt256 = UInt256.valueOf(256),
+ blockHash: Bytes32 = Bytes32.random(),
+ transactions: List = emptyList()
+ ): ExecutionPayloadV1 {
+ return ExecutionPayloadV1(
+ parentHash,
+ feeRecipient,
+ stateRoot,
+ receiptsRoot,
+ logsBloom,
+ prevRandao,
+ UInt64.valueOf(blockNumber),
+ gasLimit,
+ gasUsed,
+ timestamp,
+ extraData,
+ baseFeePerGas,
+ blockHash,
+ transactions
+ )
+ }
+}
diff --git a/coordinator/app/src/test/kotlin/net/consensys/zkevm/coordinator/blockcreation/DomainObjectMappersTest.kt b/coordinator/app/src/test/kotlin/net/consensys/zkevm/coordinator/blockcreation/DomainObjectMappersTest.kt
new file mode 100644
index 000000000..b7c69e8c2
--- /dev/null
+++ b/coordinator/app/src/test/kotlin/net/consensys/zkevm/coordinator/blockcreation/DomainObjectMappersTest.kt
@@ -0,0 +1,215 @@
+package net.consensys.zkevm.coordinator.blockcreation
+
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.TestInstance
+import org.web3j.protocol.core.methods.response.AccessListObject
+import org.web3j.protocol.core.methods.response.EthBlock
+
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+class DomainObjectMappersTest {
+
+ private val signedContractCreation = "0xf901b7808459682f07834421868080b90165608060405234801561001057600080fd5b50610" +
+ "145806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f15da729146100305" +
+ "75b600080fd5b6100a76004803603602081101561004657600080fd5b81019080803590602001906401000000008111156100635760008" +
+ "0fd5b82018360208201111561007557600080fd5b8035906020019184600183028401116401000000008311171561009757600080fd5b9" +
+ "0919293919293905050506100a9565b005b7fdb84d7c006c4de68f9c0bd50b8b81ed31f29ebeec325c872d36445c6565d757c828260405" +
+ "180806020018281038252848482818152602001925080828437600081840152601f19601f8201169050808301925050509350505050604" +
+ "05180910390a1505056fea265627a7a72315820943da63deb39c746f6663430903701509d1219fda43468ef09d46a87bf7ffb2564736f6" +
+ "c634300051000321ca05d891d090909165b7020a03780ed5954684d1db53c23387eaab3dd1d14163bcea02dbbf71810bc49dd79bc2ab03" +
+ "779d4436ba4c8232c27f04a96eb923e6e31ed8f"
+
+ private val signedEIP1159 = "0x02f86b05825d39801782520894f36f155486299ecaff2d4f5160ed5114c1f660008732123526d198bc" +
+ "80c080a039df7e43c747f44fec95ff470c9b0b1be26dfc49b20558bf7180feab68d7fdcea06d9fd7fc7f48be55735b7d64102cde9effff" +
+ "eb956de37307560908656778d93f"
+
+ private val signedEIP2930EmptyAccessList = "0x01f8f682e70481a98506f0fb17038302d26c9403555948c82a6f473b28b1e7541dc" +
+ "91d1927d52d872c68af0bb14000b8849c87a12100000000000000000000000000000000000000000000000000000000000000400000000" +
+ "00000000000000000000000000000000000000000000000000966018000000000000000000000000000000000000000000000000000000" +
+ "000000000086c6f646566693331000000000000000000000000000000000000000000000000c001a0e6681acfeadef3ed6f2817169ae45" +
+ "d3741d74a1d11cea2405258890ddf556c1ba04a70cb870f96a69048f6085f4203c0ce4ceffae6fbdec00a7fb41f370be1e213"
+
+ private val signedFrontier = "0xf88754830186a08346612494b6479d58eacc73705237571cd34c6c40c809865480a4fdacd57600000" +
+ "000000000000000000000000000000000000000000000000000000000011ca09876ddc44c63163cfe454312df7d67149b4ec3fe54debe3" +
+ "963620d01754d6e8da065c18e6b0c6e120c76b9114121a4805e67a3515c5bce17da3769e432687609f8"
+
+ private val signedEIP2930 = "0x01f9015282e70481a98506f0fb17038302d26c9403555948c82a6f473b28b1e7541dc91d1927d52d87" +
+ "2c68af0bb14000b8849c87a121000000000000000000000000000000000000000000000000000000000000004000000000000000000000" +
+ "0000000000000000000000000000000000000966018000000000000000000000000000000000000000000000000000000000000000086c" +
+ "6f646566693331000000000000000000000000000000000000000000000000f85bf85994009e7baea6a6c7c4c2dfeb977efac326af552d" +
+ "87f842a00000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000" +
+ "000000000000000000000000000101a0e6681acfeadef3ed6f2817169ae45d3741d74a1d11cea2405258890ddf556c1ba04a70cb870f96" +
+ "a69048f6085f4203c0ce4ceffae6fbdec00a7fb41f370be1e213"
+
+ @Test
+ fun encodeSignedEIP1559() {
+ val encodedTransaction = EthBlock.TransactionObject(
+ "",
+ "0x5D39",
+ "",
+ "",
+ "5",
+ "",
+ "",
+ "0xf36F155486299eCAff2D4F5160ed5114C1f66000",
+ "0x32123526D198BC",
+ "0x",
+ "0x5208",
+ "0x",
+ "",
+ "",
+ "",
+ "0x39df7e43c747f44fec95ff470c9b0b1be26dfc49b20558bf7180feab68d7fdce",
+ "0x6d9fd7fc7f48be55735b7d64102cde9effffeb956de37307560908656778d93f",
+ 0,
+ "0x2",
+ "0x17",
+ "0x0",
+ null
+ ).toBytes()
+
+ assertThat(encodedTransaction.toString()).isEqualTo(signedEIP1159)
+ }
+
+ @Test
+ fun encodeSignedFrontier() {
+ val encodedTransaction = EthBlock.TransactionObject(
+ "",
+ "0x54",
+ "",
+ "",
+ null,
+ "",
+ "",
+ "0xb6479d58Eacc73705237571CD34C6C40C8098654",
+ "0x0",
+ "0x186A0",
+ "0x466124",
+ "0xfdacd5760000000000000000000000000000000000000000000000000000000000000001",
+ "",
+ "",
+ "",
+ "0x9876ddc44c63163cfe454312df7d67149b4ec3fe54debe3963620d01754d6e8d",
+ "0x65c18e6b0c6e120c76b9114121a4805e67a3515c5bce17da3769e432687609f8",
+ 28,
+ "0x0",
+ null,
+ null,
+ null
+ ).toBytes()
+
+ assertThat(encodedTransaction.toString()).isEqualTo(signedFrontier)
+ }
+
+ @Test
+ fun encodeSignedAccessList() {
+ val accessList = listOf(
+ AccessListObject(
+ "0x9e7baea6a6c7c4c2dfeb977efac326af552d87",
+ listOf(
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "0x0000000000000000000000000000000000000000000000000000000000000001"
+ )
+ )
+ )
+
+ val encodedTransaction = EthBlock.TransactionObject(
+ "",
+ "0xA9",
+ "",
+ "",
+ "0xe704",
+ "",
+ "",
+ "0x03555948c82a6f473b28b1e7541dc91d1927d52d",
+ "0x2C68AF0BB14000",
+ "0x6f0fb1703",
+ "0x2d26c",
+ "0x9c87a12100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000" +
+ "00000000000000000000000000000966018000000000000000000000000000000000000000000000000000000000000000086c6f64" +
+ "6566693331000000000000000000000000000000000000000000000000",
+ "",
+ "",
+ "",
+ "0xe6681acfeadef3ed6f2817169ae45d3741d74a1d11cea2405258890ddf556c1b",
+ "0x4a70cb870f96a69048f6085f4203c0ce4ceffae6fbdec00a7fb41f370be1e213",
+ 0x1,
+ "0x1",
+ null,
+ null,
+ accessList
+ ).toBytes()
+
+ assertThat(encodedTransaction.toString()).isEqualTo(signedEIP2930)
+ }
+
+ @Test
+ fun encodeSignedAccessListEmptyList() {
+ val emptyAccessList = emptyList()
+
+ val encodedTransaction = EthBlock.TransactionObject(
+ "",
+ "0xA9",
+ "",
+ "",
+ "0xe704",
+ "",
+ "",
+ "0x03555948c82a6f473b28b1e7541dc91d1927d52d",
+ "0x2C68AF0BB14000",
+ "0x6f0fb1703",
+ "0x2d26c",
+ "0x9c87a12100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000" +
+ "00000000000000000000000000000966018000000000000000000000000000000000000000000000000000000000000000086c6f64" +
+ "6566693331000000000000000000000000000000000000000000000000",
+ "",
+ "",
+ "",
+ "0xe6681acfeadef3ed6f2817169ae45d3741d74a1d11cea2405258890ddf556c1b",
+ "0x4a70cb870f96a69048f6085f4203c0ce4ceffae6fbdec00a7fb41f370be1e213",
+ 0x1,
+ "0x1",
+ null,
+ null,
+ emptyAccessList
+ ).toBytes()
+
+ assertThat(encodedTransaction.toString()).isEqualTo(signedEIP2930EmptyAccessList)
+ }
+
+ @Test
+ fun encodeContractCreation() {
+ val encodedTransaction = EthBlock.TransactionObject(
+ "",
+ "0x0",
+ "",
+ "",
+ null,
+ "",
+ "",
+ null,
+ "0x0",
+ "0x59682f07",
+ "0x442186",
+ "0x608060405234801561001057600080fd5b50610145806100206000396000f3fe608060405234801561001057600080fd5b50" +
+ "6004361061002b5760003560e01c8063f15da72914610030575b600080fd5b6100a76004803603602081101561004657600080fd5b" +
+ "810190808035906020019064010000000081111561006357600080fd5b82018360208201111561007557600080fd5b803590602001" +
+ "9184600183028401116401000000008311171561009757600080fd5b90919293919293905050506100a9565b005b7fdb84d7c006c4" +
+ "de68f9c0bd50b8b81ed31f29ebeec325c872d36445c6565d757c828260405180806020018281038252848482818152602001925080" +
+ "828437600081840152601f19601f820116905080830192505050935050505060405180910390a1505056fea265627a7a7231582094" +
+ "3da63deb39c746f6663430903701509d1219fda43468ef09d46a87bf7ffb2564736f6c63430005100032",
+ "",
+ "",
+ "",
+ "0x5d891d090909165b7020a03780ed5954684d1db53c23387eaab3dd1d14163bce",
+ "0x2dbbf71810bc49dd79bc2ab03779d4436ba4c8232c27f04a96eb923e6e31ed8f",
+ 28,
+ "0x0",
+ null,
+ null,
+ null
+ ).toBytes()
+
+ assertThat(encodedTransaction.toString()).isEqualTo(signedContractCreation)
+ }
+}
diff --git a/coordinator/app/src/test/kotlin/net/consensys/zkevm/coordinator/blockcreation/TracesFilesManagerTest.kt b/coordinator/app/src/test/kotlin/net/consensys/zkevm/coordinator/blockcreation/TracesFilesManagerTest.kt
new file mode 100644
index 000000000..73b93e3fc
--- /dev/null
+++ b/coordinator/app/src/test/kotlin/net/consensys/zkevm/coordinator/blockcreation/TracesFilesManagerTest.kt
@@ -0,0 +1,154 @@
+package net.consensys.zkevm.coordinator.blockcreation
+
+import io.vertx.core.Vertx
+import net.consensys.linea.traces.TracesFiles
+import org.apache.tuweni.bytes.Bytes32
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.RepeatedTest
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+import org.junit.jupiter.api.io.TempDir
+import tech.pegasys.teku.infrastructure.unsigned.UInt64
+import java.io.FileNotFoundException
+import java.nio.file.Files
+import java.nio.file.Path
+import java.util.concurrent.ExecutionException
+import kotlin.io.path.createFile
+import kotlin.io.path.exists
+import kotlin.time.Duration.Companion.milliseconds
+
+class TracesFilesManagerTest {
+ private val tracesVersion = "0.0.1"
+ private val tracesFileExtension = "json.gz"
+ private lateinit var vertx: Vertx
+ private lateinit var tracesDir: Path
+ private lateinit var nonCanonicalBlocksTracesDir: Path
+ private lateinit var tracesFilesManager: TracesFilesManager
+ private val block1Hash =
+ Bytes32.fromHexString("0x0000000000000000000000000000000000000000000000000000000000000001")
+ private val block2Hash1 =
+ Bytes32.fromHexString("0x00000000000000000000000000000000000000000000000000000000000000a1")
+ private val block2Hash2 =
+ Bytes32.fromHexString("0x00000000000000000000000000000000000000000000000000000000000000a2")
+ private val block20Hash =
+ Bytes32.fromHexString("0x0000000000000000000000000000000000000000000000000000000000000001")
+ private lateinit var block1TracesFile: Path
+ private lateinit var block2TracesFile1: Path
+ private lateinit var block2TracesFile2: Path
+ private lateinit var block20TracesFile: Path
+ private lateinit var config: TracesFilesManager.Config
+
+ @BeforeEach
+ fun beforeEach(@TempDir tmpTestDir: Path) {
+ tracesDir = tmpTestDir.resolve("raw-traces")
+ nonCanonicalBlocksTracesDir = tmpTestDir.resolve("non-canonical-raw-traces")
+ Files.createDirectories(tracesDir)
+
+ val block1TracesFileName = TracesFiles.rawTracesFileNameSupplierV1(
+ UInt64.ONE.longValue().toULong(),
+ block1Hash,
+ tracesVersion,
+ tracesFileExtension
+ )
+ val block2TracesFile1Name = TracesFiles.rawTracesFileNameSupplierV1(
+ UInt64.valueOf(2).longValue().toULong(),
+ block2Hash1,
+ tracesVersion,
+ tracesFileExtension
+ )
+ val block2TracesFile2Name = TracesFiles.rawTracesFileNameSupplierV1(
+ UInt64.valueOf(2).longValue().toULong(),
+ block2Hash2,
+ tracesVersion,
+ tracesFileExtension
+ )
+ val block20TracesFileName = TracesFiles.rawTracesFileNameSupplierV1(
+ UInt64.valueOf(20).longValue().toULong(),
+ block2Hash1,
+ tracesVersion,
+ tracesFileExtension
+ )
+ block1TracesFile =
+ tracesDir.resolve(Path.of(block1TracesFileName))
+ block2TracesFile1 =
+ tracesDir.resolve(Path.of(block2TracesFile1Name))
+ block2TracesFile2 =
+ tracesDir.resolve(Path.of(block2TracesFile2Name))
+ block20TracesFile =
+ tracesDir.resolve(Path.of(block20TracesFileName))
+
+ vertx = Vertx.vertx()
+ config =
+ TracesFilesManager.Config(
+ tracesDir,
+ nonCanonicalBlocksTracesDir,
+ pollingInterval = 10.milliseconds,
+ tracesGenerationTimeout = 200.milliseconds,
+ tracesFileExtension = tracesFileExtension,
+ tracesEngineVersion = tracesVersion,
+ createNonCanonicalTracesDirIfDoesNotExist = true
+ )
+ tracesFilesManager = TracesFilesManager(vertx, config, TracesFiles::rawTracesFileNameSupplierV1)
+ }
+
+ @Test
+ fun `waitRawTracesGenerationOf - waits until traces file is found`() {
+ // write file with wrong extension
+ val inprogressFile =
+ tracesDir.resolve(Path.of("1-${block1Hash.toHexString()}.inprogress")).createFile()
+ assertThat(inprogressFile).exists()
+
+ val future = tracesFilesManager.waitRawTracesGenerationOf(UInt64.ONE, block1Hash)
+ vertx.setTimer((config.tracesGenerationTimeout.inWholeMilliseconds / 2)) {
+ Files.createFile(block1TracesFile)
+ }
+
+ assertThat(future.get()).endsWith(block1TracesFile.toString())
+ }
+
+ @RepeatedTest(10)
+ fun `waitRawTracesGenerationOf - returns error after timeout`() {
+ val future = tracesFilesManager.waitRawTracesGenerationOf(UInt64.valueOf(2), block2Hash1)
+ val exception = assertThrows { future.get() }
+ assertThat(exception.cause).isInstanceOf(FileNotFoundException::class.java)
+ assertThat(exception.message).matches(".* File matching '2-$block2Hash1.* not found .*")
+ }
+
+ @Test
+ fun `cleanNonCanonicalSiblingsByHeight - returns error when file to keep is not found`() {
+ val future = tracesFilesManager.cleanNonCanonicalSiblingsByHeight(UInt64.ONE, block1Hash)
+
+ assertThat(future.get()).isEmpty()
+ }
+
+ @Test
+ fun `cleanNonCanonicalSiblingsByHeight - removes found siblings`() {
+ Files.createFile(block2TracesFile1)
+ Files.createFile(block2TracesFile2)
+ Files.createFile(block20TracesFile)
+ assertThat(Files.exists(block2TracesFile1)).isTrue()
+ assertThat(Files.exists(block2TracesFile2)).isTrue()
+ assertThat(Files.exists(block20TracesFile)).isTrue()
+
+ tracesFilesManager.cleanNonCanonicalSiblingsByHeight(UInt64.valueOf(2), block2Hash1).get()
+
+ assertThat(block2TracesFile1).exists()
+ assertThat(block2TracesFile2).doesNotExist()
+ assertThat(block20TracesFile).exists()
+ }
+
+ // @Test
+ // @Disabled("This feature is not necessary anymore. Just keeping the test in case we need to revert")
+ // fun `waitRawTracesGenerationOf - cleans non canonical siblings`() {
+ // Files.createFile(block2TracesFile1)
+ // Files.createFile(block2TracesFile2)
+ // assertThat(block2TracesFile1).exists()
+ // assertThat(block2TracesFile2).exists()
+ //
+ // tracesFilesManager.waitRawTracesGenerationOf(UInt64.valueOf(2), block2Hash1).get()
+ //
+ // assertThat(block2TracesFile1).exists()
+ // assertThat(block2TracesFile2).doesNotExist()
+ // }
+}
diff --git a/coordinator/app/src/test/kotlin/net/consensys/zkevm/ethereum/coordination/dynamicgasprice/DynamicGasPriceServiceTest.kt b/coordinator/app/src/test/kotlin/net/consensys/zkevm/ethereum/coordination/dynamicgasprice/DynamicGasPriceServiceTest.kt
new file mode 100644
index 000000000..b3e332c65
--- /dev/null
+++ b/coordinator/app/src/test/kotlin/net/consensys/zkevm/ethereum/coordination/dynamicgasprice/DynamicGasPriceServiceTest.kt
@@ -0,0 +1,106 @@
+package net.consensys.zkevm.ethereum.coordination.dynamicgasprice
+
+import io.vertx.core.Vertx
+import io.vertx.junit5.Timeout
+import io.vertx.junit5.VertxExtension
+import io.vertx.junit5.VertxTestContext
+import net.consensys.linea.FeeHistory
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.TestInstance
+import org.junit.jupiter.api.extension.ExtendWith
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import tech.pegasys.teku.infrastructure.async.SafeFuture
+import java.math.BigInteger
+import java.util.concurrent.TimeUnit
+import kotlin.time.Duration.Companion.milliseconds
+
+@ExtendWith(VertxExtension::class)
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+class DynamicGasPriceServiceTest {
+ private val feeHistory = FeeHistory(
+ oldestBlock = BigInteger.valueOf(100),
+ baseFeePerGas = listOf(100, 110, 120, 130, 140).map { BigInteger.valueOf(it.toLong()) },
+ reward = listOf(1000, 1100, 1200, 1300).map { listOf(BigInteger.valueOf(it.toLong())) },
+ gasUsedRatio = listOf(0.25.toBigDecimal(), 0.5.toBigDecimal(), 0.75.toBigDecimal(), 0.9.toBigDecimal())
+ )
+
+ @Test
+ @Timeout(10, timeUnit = TimeUnit.SECONDS)
+ fun start_startsPollingProcess(vertx: Vertx, testContext: VertxTestContext) {
+ val pollingInterval = 10.milliseconds
+ val gasPriceCap = BigInteger("9000000000000")
+ val calculatedL2GasPrice = gasPriceCap.minus(BigInteger.ONE)
+
+ val mockFeesFetcher = mock() {
+ on { getL1EthGasPriceData() } doReturn SafeFuture.completedFuture(feeHistory)
+ }
+ val mockFeesCalculator = mock {
+ on { calculateFees(eq(feeHistory)) } doReturn calculatedL2GasPrice
+ }
+ val mockGasPriceUpdater = mock() {
+ on { updateMinerGasPrice(any()) } doAnswer { SafeFuture.completedFuture(listOf(Unit)) }
+ }
+ val monitor =
+ DynamicGasPriceService(
+ DynamicGasPriceService.Config(
+ pollingInterval,
+ gasPriceCap
+ ),
+ vertx,
+ mockFeesFetcher,
+ mockFeesCalculator,
+ mockGasPriceUpdater
+ )
+ monitor.start().thenApply {
+ vertx.setTimer(100) {
+ testContext
+ .verify {
+ monitor.stop()
+ verify(mockFeesFetcher, atLeastOnce()).getL1EthGasPriceData()
+ verify(mockFeesCalculator, atLeastOnce()).calculateFees(feeHistory)
+ verify(mockGasPriceUpdater, atLeastOnce()).updateMinerGasPrice(calculatedL2GasPrice)
+ }
+ .completeNow()
+ }
+ }
+ }
+
+ @Test
+ @Timeout(10, timeUnit = TimeUnit.SECONDS)
+ fun udpatePrice_defaultsToPriceCapCalculatedPriceGoesOverLimit(vertx: Vertx) {
+ val pollingInterval = 10.milliseconds
+ val gasPriceCap = BigInteger.valueOf(10_000_000_000L) // 10 GWei
+ val calculatedL2GasPrice = gasPriceCap.plus(BigInteger.ONE)
+
+ val mockFeesFetcher = mock() {
+ on { getL1EthGasPriceData() } doReturn SafeFuture.completedFuture(feeHistory)
+ }
+ val mockFeesCalculator = mock {
+ on { calculateFees(any()) } doReturn calculatedL2GasPrice
+ }
+ val mockGasPriceUpdater = mock() {
+ on { updateMinerGasPrice(any()) } doAnswer { SafeFuture.completedFuture(listOf(Unit)) }
+ }
+
+ val monitor =
+ DynamicGasPriceService(
+ DynamicGasPriceService.Config(
+ pollingInterval,
+ gasPriceCap
+ ),
+ vertx,
+ mockFeesFetcher,
+ mockFeesCalculator,
+ mockGasPriceUpdater
+ )
+
+ monitor.tick().get()
+ verify(mockGasPriceUpdater).updateMinerGasPrice(gasPriceCap)
+ }
+}
diff --git a/coordinator/app/src/test/kotlin/net/consensys/zkevm/ethereum/coordination/dynamicgasprice/FeeHistoryFetcherImplTest.kt b/coordinator/app/src/test/kotlin/net/consensys/zkevm/ethereum/coordination/dynamicgasprice/FeeHistoryFetcherImplTest.kt
new file mode 100644
index 000000000..6a5ebb2e1
--- /dev/null
+++ b/coordinator/app/src/test/kotlin/net/consensys/zkevm/ethereum/coordination/dynamicgasprice/FeeHistoryFetcherImplTest.kt
@@ -0,0 +1,97 @@
+package net.consensys.zkevm.ethereum.coordination.dynamicgasprice
+
+import io.vertx.junit5.Timeout
+import io.vertx.junit5.VertxExtension
+import io.vertx.junit5.VertxTestContext
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.RepeatedTest
+import org.junit.jupiter.api.extension.ExtendWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+import org.web3j.protocol.Web3j
+import org.web3j.protocol.core.DefaultBlockParameter
+import org.web3j.protocol.core.methods.response.EthBlockNumber
+import org.web3j.protocol.core.methods.response.EthFeeHistory
+import org.web3j.protocol.core.methods.response.EthGasPrice
+import tech.pegasys.teku.infrastructure.async.SafeFuture
+import java.math.BigInteger
+import java.util.*
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.TimeUnit
+
+@ExtendWith(VertxExtension::class)
+class FeeHistoryFetcherImplTest {
+ private val feeHistoryBlockCount = 10u
+ private val feeHistoryRewardPercentile = 15.0
+
+ @RepeatedTest(1)
+ @Timeout(10, timeUnit = TimeUnit.SECONDS)
+ fun feeHistoryFetcherImpl_returnsFeeHistoryData(testContext: VertxTestContext) {
+ val l1ClientMock = createMockedWeb3jClient()
+
+ val feeHistoryFetcherImpl =
+ FeeHistoryFetcherImpl(
+ l1ClientMock,
+ FeeHistoryFetcherImpl.Config(
+ feeHistoryBlockCount,
+ feeHistoryRewardPercentile
+ )
+ )
+
+ feeHistoryFetcherImpl.getL1EthGasPriceData()
+ .thenApply {
+ testContext
+ .verify {
+ assertThat(it).isNotNull
+ assertThat(it.baseFeePerGas.size.toUInt()).isEqualTo(feeHistoryBlockCount + 1u)
+ assertThat(it.reward.size.toUInt()).isEqualTo(feeHistoryBlockCount)
+ assertThat(it.gasUsedRatio.size.toUInt()).isEqualTo(feeHistoryBlockCount)
+ it.reward.forEach {
+ assertThat(it.size).isEqualTo(1)
+ }
+ }
+ .completeNow()
+ }
+ }
+
+ private fun createMockedWeb3jClient(): Web3j {
+ val web3jClient = mock(defaultAnswer = Mockito.RETURNS_DEEP_STUBS)
+ whenever(
+ web3jClient
+ .ethFeeHistory(
+ ArgumentMatchers.eq(feeHistoryBlockCount.toInt()),
+ ArgumentMatchers.eq(DefaultBlockParameter.valueOf("latest")),
+ ArgumentMatchers.eq(listOf(feeHistoryRewardPercentile))
+ )
+ .sendAsync()
+ )
+ .thenAnswer {
+ val feeHistoryResponse = EthFeeHistory()
+ val feeHistory = EthFeeHistory.FeeHistory()
+ feeHistory.setReward((1000 until 1010).map { listOf(it.toString()) })
+ feeHistory.setBaseFeePerGas((10000 until 10011).map { it.toString() })
+ feeHistory.gasUsedRatio = (10 until 20).map { it / 100.0 }
+ feeHistoryResponse.result = feeHistory
+ feeHistory.setOldestBlock("0x16")
+ SafeFuture.completedFuture(feeHistoryResponse)
+ }
+ whenever(
+ web3jClient
+ .ethGasPrice()
+ .sendAsync()
+ )
+ .thenAnswer {
+ val gasPriceResponse = EthGasPrice()
+ gasPriceResponse.result = "0x100"
+ SafeFuture.completedFuture(gasPriceResponse)
+ }
+ val mockBlockNumberReturn = mock()
+ whenever(mockBlockNumberReturn.blockNumber).thenReturn(BigInteger.valueOf(13L))
+ whenever(web3jClient.ethBlockNumber().sendAsync())
+ .thenReturn(CompletableFuture.completedFuture(mockBlockNumberReturn))
+
+ return web3jClient
+ }
+}
diff --git a/coordinator/app/src/test/kotlin/net/consensys/zkevm/ethereum/coordination/dynamicgasprice/GasPriceUpdaterImplTest.kt b/coordinator/app/src/test/kotlin/net/consensys/zkevm/ethereum/coordination/dynamicgasprice/GasPriceUpdaterImplTest.kt
new file mode 100644
index 000000000..21c9e6ffb
--- /dev/null
+++ b/coordinator/app/src/test/kotlin/net/consensys/zkevm/ethereum/coordination/dynamicgasprice/GasPriceUpdaterImplTest.kt
@@ -0,0 +1,120 @@
+package net.consensys.zkevm.ethereum.coordination.dynamicgasprice
+
+import com.github.tomakehurst.wiremock.WireMockServer
+import com.github.tomakehurst.wiremock.client.WireMock.containing
+import com.github.tomakehurst.wiremock.client.WireMock.ok
+import com.github.tomakehurst.wiremock.client.WireMock.post
+import io.micrometer.core.instrument.simple.SimpleMeterRegistry
+import io.vertx.core.Vertx
+import io.vertx.core.http.HttpClientOptions
+import io.vertx.core.http.HttpVersion
+import io.vertx.core.json.JsonObject
+import io.vertx.junit5.Timeout
+import io.vertx.junit5.VertxExtension
+import io.vertx.junit5.VertxTestContext
+import net.consensys.linea.jsonrpc.client.VertxHttpJsonRpcClient
+import net.consensys.linea.jsonrpc.client.VertxHttpJsonRpcClientFactory
+import org.apache.logging.log4j.Logger
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.RepeatedTest
+import org.junit.jupiter.api.extension.ExtendWith
+import org.mockito.Mockito
+import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+import java.math.BigInteger
+import java.net.URI
+import java.net.URL
+import java.util.concurrent.TimeUnit
+
+@ExtendWith(VertxExtension::class)
+class GasPriceUpdaterImplTest {
+ private val recipientHostnames = listOf(
+ "http://localhost:18545/",
+ "http://localhost:18546/",
+ "http://localhost:18647/"
+ )
+
+ private fun wiremockStubForPost(response: JsonObject, wiremock: WireMockServer) {
+ wiremock.stubFor(
+ post("/")
+ .withHeader("Content-Type", containing("application/json"))
+ .willReturn(
+ ok()
+ .withHeader("Content-type", "application/json")
+ .withBody(response.toString().toByteArray())
+ )
+ )
+ }
+
+ private fun createMockedHttpJsonRpcClientFactory(vertx: Vertx): VertxHttpJsonRpcClientFactory {
+ val httpJsonRpcClientFactory = mock(defaultAnswer = Mockito.RETURNS_DEEP_STUBS)
+
+ whenever(
+ httpJsonRpcClientFactory
+ .create(
+ any(),
+ anyOrNull(),
+ anyOrNull(),
+ any(),
+ anyOrNull(),
+ anyOrNull()
+ )
+ ).thenAnswer { invocation ->
+ val endpointUrl = invocation.getArgument(0)
+ val wiremock = WireMockServer(endpointUrl.port)
+ wiremock.start()
+
+ val response =
+ JsonObject.of(
+ "jsonrpc",
+ "2.0",
+ "id",
+ 1,
+ "result",
+ endpointUrl.port % 2 == 0
+ )
+
+ wiremockStubForPost(response, wiremock)
+
+ val uri = URI(endpointUrl.toString())
+ val clientOptions =
+ HttpClientOptions()
+ .setKeepAlive(true)
+ .setProtocolVersion(HttpVersion.HTTP_1_1)
+ .setMaxPoolSize(10)
+ .setDefaultHost(uri.host)
+ .setDefaultPort(uri.port)
+ .setLogActivity(true)
+ val httpClient = vertx.createHttpClient(clientOptions)
+ val meterRegistry = SimpleMeterRegistry()
+ VertxHttpJsonRpcClient(httpClient, "/", meterRegistry)
+ }
+
+ return httpJsonRpcClientFactory
+ }
+
+ @RepeatedTest(1)
+ @Timeout(10, timeUnit = TimeUnit.SECONDS)
+ fun gasPriceUpdaterImpl_sendsMinerSetGasPriceToRecipients(vertx: Vertx, testContext: VertxTestContext) {
+ val httpJsonRpcClientFactoryMock = createMockedHttpJsonRpcClientFactory(vertx)
+
+ val l2GasPriceUpdaterImpl =
+ GasPriceUpdaterImpl(
+ httpJsonRpcClientFactoryMock,
+ GasPriceUpdaterImpl.Config(
+ this.recipientHostnames.map { URL(it) }
+ )
+ )
+
+ l2GasPriceUpdaterImpl.updateMinerGasPrice(BigInteger.valueOf(3000000000L))
+ .thenApply {
+ testContext
+ .verify {
+ assertThat(it).isEqualTo((0 until this.recipientHostnames.size).map {})
+ }
+ .completeNow()
+ }
+ }
+}
diff --git a/coordinator/app/src/test/kotlin/net/consensys/zkevm/ethereum/coordination/dynamicgasprice/GasUsageRatioWeightedAverageFeesCalculatorTest.kt b/coordinator/app/src/test/kotlin/net/consensys/zkevm/ethereum/coordination/dynamicgasprice/GasUsageRatioWeightedAverageFeesCalculatorTest.kt
new file mode 100644
index 000000000..4a910ed97
--- /dev/null
+++ b/coordinator/app/src/test/kotlin/net/consensys/zkevm/ethereum/coordination/dynamicgasprice/GasUsageRatioWeightedAverageFeesCalculatorTest.kt
@@ -0,0 +1,105 @@
+package net.consensys.zkevm.ethereum.coordination.dynamicgasprice
+
+import net.consensys.linea.FeeHistory
+import org.assertj.core.api.Assertions
+import org.junit.jupiter.api.Test
+import java.math.BigDecimal
+import java.math.BigInteger
+
+class GasUsageRatioWeightedAverageFeesCalculatorTest {
+ val feesCalculator =
+ GasUsageRatioWeightedAverageFeesCalculator(
+ GasUsageRatioWeightedAverageFeesCalculator.Config(
+ baseFeeCoefficient = BigDecimal("0.1"),
+ priorityFeeCoefficient = BigDecimal("1.2")
+ )
+ )
+
+ @Test
+ fun calculateFees_singleBlockHistory() {
+ val feeHistory = FeeHistory(
+ oldestBlock = BigInteger.valueOf(100),
+ baseFeePerGas = listOf(100, 110).map { BigInteger.valueOf(it.toLong()) },
+ reward = listOf(1000).map { listOf(BigInteger.valueOf(it.toLong())) },
+ gasUsedRatio = listOf(0.25.toBigDecimal())
+ )
+ // (100*0.1 + 1000*1.2)*0.25/0.25 = 1210
+ val calculatedL2GasPrice = feesCalculator.calculateFees(feeHistory)
+ Assertions.assertThat(calculatedL2GasPrice).isEqualTo(BigInteger.valueOf(1210))
+ }
+
+ @Test
+ fun calculateFees_singleBlockHistory_zeroUsageRatio() {
+ val feeHistory = FeeHistory(
+ oldestBlock = BigInteger.valueOf(100),
+ baseFeePerGas = listOf(100, 110).map { BigInteger.valueOf(it.toLong()) },
+ reward = listOf(1000).map { listOf(BigInteger.valueOf(it.toLong())) },
+ gasUsedRatio = listOf(0.0.toBigDecimal())
+ )
+ // (100*0.1 + 1000*1.2) = 1210
+ val calculatedL2GasPrice = feesCalculator.calculateFees(feeHistory)
+ Assertions.assertThat(calculatedL2GasPrice).isEqualTo(BigInteger.valueOf(1210))
+ }
+
+ @Test
+ fun calculateFees_MultipleBlockHistory() {
+ val feeHistory = FeeHistory(
+ oldestBlock = BigInteger.valueOf(100),
+ baseFeePerGas = listOf(100, 110, 120, 130, 140).map { BigInteger.valueOf(it.toLong()) },
+ reward = listOf(1000, 1100, 1200, 1300).map { listOf(BigInteger.valueOf(it.toLong())) },
+ gasUsedRatio = listOf(0.25.toBigDecimal(), 0.5.toBigDecimal(), 0.75.toBigDecimal(), 0.9.toBigDecimal())
+ )
+
+ // Weighted Average:
+ // ((100*0.1 + 1000*1.2)*0.25 + (110*0.1 + 1100*1.2)*0.50 + (120*0.1 + 1200*1.2)*0.75 + (130*0.1 + 1300*1.2)*0.9)/(0.25+0.5+0.75+0.9) = 1446.95833333
+ val calculatedL2GasPrice = feesCalculator.calculateFees(feeHistory)
+ Assertions.assertThat(calculatedL2GasPrice).isEqualTo(BigInteger.valueOf(1447))
+ }
+
+ @Test
+ fun calculateFees_equalUsageRatio_shouldEqualSimpleAverage() {
+ val feeHistory = FeeHistory(
+ oldestBlock = BigInteger.valueOf(100),
+ baseFeePerGas = listOf(100, 110, 120, 130, 140).map { BigInteger.valueOf(it.toLong()) },
+ reward = listOf(1000, 1100, 1200, 1300).map { listOf(BigInteger.valueOf(it.toLong())) },
+ gasUsedRatio = listOf(0.5.toBigDecimal(), 0.5.toBigDecimal(), 0.5.toBigDecimal(), 0.5.toBigDecimal())
+ )
+
+ // Simple Average:
+ // ((100*0.1 + 1000*1.2)*0.5 + (110*0.1 + 1100*1.2)*0.5 + (120*0.1 + 1200*1.2)*0.5 + (130*0.1 + 1300*1.2)*0.5)/(0.5+0.5+0.5+0.5) = 1391.5
+ val calculatedL2GasPrice = feesCalculator.calculateFees(feeHistory)
+ Assertions.assertThat(calculatedL2GasPrice).isEqualTo(BigInteger.valueOf(1392))
+ }
+
+ @Test
+ fun calculateFees_zeroUsage_emptyL1_blocks_should_return_SimpleAverage() {
+ val feeHistory = FeeHistory(
+ oldestBlock = BigInteger.valueOf(100),
+ baseFeePerGas = listOf(100, 110, 120, 130, 140).map { BigInteger.valueOf(it.toLong()) },
+ reward = listOf(1000, 1100, 1200, 1300).map { listOf(BigInteger.valueOf(it.toLong())) },
+ gasUsedRatio = listOf(0.0.toBigDecimal(), 0.0.toBigDecimal(), 0.0.toBigDecimal(), 0.0.toBigDecimal())
+ )
+
+ // Simple Average:
+ // ((100*0.1 + 1000*1.2)*1 + (110*0.1 + 1100*1.2)*1 + (120*0.1 + 1200*1.2)*1 + (130*0.1 + 1300*1.2)*1)/4) = 1391.5
+ val calculatedL2GasPrice = feesCalculator.calculateFees(feeHistory)
+ Assertions.assertThat(calculatedL2GasPrice).isEqualTo(BigInteger.valueOf(1392))
+ }
+
+ @Test
+ fun calculateFees_partialZeroUsage_emptyL1_blocks_should_return_SimpleAverage() {
+ // Very unlikely scenario in L1 network with activity.
+ // Only happen for local dev or test networks, so we don't need to optimize for it.
+ val feeHistory = FeeHistory(
+ oldestBlock = BigInteger.valueOf(100),
+ baseFeePerGas = listOf(100, 110, 120, 130, 140).map { BigInteger.valueOf(it.toLong()) },
+ reward = listOf(1000, 1100, 1200, 1300).map { listOf(BigInteger.valueOf(it.toLong())) },
+ gasUsedRatio = listOf(0.75.toBigDecimal(), 0.0.toBigDecimal(), 0.75.toBigDecimal(), 0.0.toBigDecimal())
+ )
+
+ // Weighted Average:
+ // ((100*0.1 + 1000*1.2)*0.75 + (110*0.1 + 1100*1.2)*0.0 + (120*0.1 + 1200*1.2)*0.75 + (130*0.1 + 1300*1.2)*0.0)/(0.75+0.0+0.75+0.0) = 1331
+ val calculatedL2GasPrice = feesCalculator.calculateFees(feeHistory)
+ Assertions.assertThat(calculatedL2GasPrice).isEqualTo(BigInteger.valueOf(1331))
+ }
+}
diff --git a/coordinator/app/src/test/kotlin/net/consensys/zkevm/ethereum/coordination/dynamicgasprice/WMAFeesCalculatorTest.kt b/coordinator/app/src/test/kotlin/net/consensys/zkevm/ethereum/coordination/dynamicgasprice/WMAFeesCalculatorTest.kt
new file mode 100644
index 000000000..5601e3ac4
--- /dev/null
+++ b/coordinator/app/src/test/kotlin/net/consensys/zkevm/ethereum/coordination/dynamicgasprice/WMAFeesCalculatorTest.kt
@@ -0,0 +1,56 @@
+package net.consensys.zkevm.ethereum.coordination.dynamicgasprice
+
+import io.vertx.junit5.VertxExtension
+import net.consensys.linea.FeeHistory
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import java.math.BigDecimal
+import java.math.BigInteger
+
+@ExtendWith(VertxExtension::class)
+class WMAFeesCalculatorTest {
+ @Test
+ fun `calculateFees weighted moving average of priority fee`() {
+ val wmaFeesCalculator =
+ WMAFeesCalculator(
+ WMAFeesCalculator.Config(
+ baseFeeCoefficient = BigDecimal("0.1"),
+ priorityFeeWmaCoefficient = BigDecimal("1.2")
+ )
+ )
+
+ val feeHistory = FeeHistory(
+ oldestBlock = BigInteger.valueOf(100),
+ baseFeePerGas = listOf(100, 110, 120, 130, 140).map { BigInteger.valueOf(it.toLong()) },
+ reward = listOf(1000, 1100, 1200, 1300).map { listOf(BigInteger.valueOf(it.toLong())) },
+ gasUsedRatio = listOf(0.25.toBigDecimal(), 0.5.toBigDecimal(), 0.75.toBigDecimal(), 0.9.toBigDecimal())
+ )
+
+ // WMA = (140*0.1) + ((1000*0.25*1 + 1100*0.5*2 + 1200*0.75*3 + 1300*0.9*4) / (0.25*1 + 0.5*2 + 0.75*3 + 0.9*4)) * 1.2 = 1489.49295
+ val calculatedL2GasPrice = wmaFeesCalculator.calculateFees(feeHistory)
+ assertThat(calculatedL2GasPrice).isEqualTo(BigInteger.valueOf(1489))
+ }
+
+ @Test
+ fun `calculateFees supports empty usage ratio`() {
+ val wmaFeesCalculator =
+ WMAFeesCalculator(
+ WMAFeesCalculator.Config(
+ baseFeeCoefficient = BigDecimal("0.1"),
+ priorityFeeWmaCoefficient = BigDecimal("1.2")
+ )
+ )
+
+ val feeHistory = FeeHistory(
+ oldestBlock = BigInteger.valueOf(100),
+ baseFeePerGas = listOf(100, 110, 120, 130, 140).map { BigInteger.valueOf(it.toLong()) },
+ reward = listOf(1000, 1100, 1200, 1300).map { listOf(BigInteger.valueOf(it.toLong())) },
+ gasUsedRatio = listOf(0.0.toBigDecimal(), 0.0.toBigDecimal(), 0.0.toBigDecimal(), 0.0.toBigDecimal())
+ )
+
+ // WMA = (140*0.1) = 14.0
+ val calculatedL2GasPrice = wmaFeesCalculator.calculateFees(feeHistory)
+ assertThat(calculatedL2GasPrice).isEqualTo(BigInteger.valueOf(14))
+ }
+}
diff --git a/coordinator/app/src/test/kotlin/net/consensys/zkevm/ethereum/coordination/messageanchoring/L1EventQuerierImplTest.kt b/coordinator/app/src/test/kotlin/net/consensys/zkevm/ethereum/coordination/messageanchoring/L1EventQuerierImplTest.kt
new file mode 100644
index 000000000..885952d97
--- /dev/null
+++ b/coordinator/app/src/test/kotlin/net/consensys/zkevm/ethereum/coordination/messageanchoring/L1EventQuerierImplTest.kt
@@ -0,0 +1,850 @@
+package net.consensys.zkevm.ethereum.coordination.messageanchoring
+
+import io.vertx.core.Vertx
+import io.vertx.junit5.Timeout
+import io.vertx.junit5.VertxExtension
+import io.vertx.junit5.VertxTestContext
+import net.consensys.linea.contract.ZkEvmV2
+import org.apache.tuweni.bytes.Bytes32
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.mockito.Mockito
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+import org.web3j.abi.EventEncoder
+import org.web3j.abi.FunctionEncoder
+import org.web3j.abi.TypeEncoder
+import org.web3j.abi.datatypes.Address
+import org.web3j.abi.datatypes.generated.Uint256
+import org.web3j.protocol.Web3j
+import org.web3j.protocol.core.DefaultBlockParameter
+import org.web3j.protocol.core.methods.request.EthFilter
+import org.web3j.protocol.core.methods.response.EthBlock
+import org.web3j.protocol.core.methods.response.EthLog
+import org.web3j.protocol.core.methods.response.EthLog.LogResult
+import org.web3j.protocol.core.methods.response.Log
+import java.math.BigInteger
+import java.util.concurrent.TimeUnit
+import kotlin.random.Random
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.seconds
+
+@ExtendWith(VertxExtension::class)
+class L1EventQuerierImplTest {
+ private val testContractAddress = "0x6d976c9b8ceee705d4fe8699b44e5eb58242f484"
+ private val testToAddress: Address = Address("0x087b027b0573D4f01345eF8D081E0E7d3B378d14")
+ private val testFromAddress = Address("0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5")
+ private val testFee = Uint256(12345)
+ private val testValue = Uint256(123456789)
+ private val testNonce = Uint256(1)
+ private val callData: org.web3j.abi.datatypes.DynamicBytes =
+ org.web3j.abi.datatypes.DynamicBytes("".encodeToByteArray())
+
+ private val logIndexStart = 1
+ private val blockInitialEventIsOn = 19
+ private val maxMessagesToAnchor = 100u
+ private val pollingInterval = "1s"
+ private val earliestL1Block = BigInteger.valueOf(0)
+ private val maxEventScrapingTime: Duration = 9.seconds
+ private val blockRangeLoopLimit = 100u
+
+ @Test
+ @Timeout(10, timeUnit = TimeUnit.SECONDS)
+ fun nullHashAndNoMessages_returnsNoEvents(vertx: Vertx, testContext: VertxTestContext) {
+ val l1ClientMock = mock(defaultAnswer = Mockito.RETURNS_DEEP_STUBS)
+ val mockedEthBlock = mock(defaultAnswer = Mockito.RETURNS_DEEP_STUBS)
+ whenever(mockedEthBlock.block.number).thenReturn(BigInteger.valueOf(20))
+
+ whenever(l1ClientMock.ethGetBlockByNumber(any(), any()).send())
+ .thenReturn(mockedEthBlock)
+
+ val emptyEvents: List> = listOf()
+ val mockLogs = mock()
+ whenever(mockLogs.logs).thenReturn(emptyEvents)
+ whenever(l1ClientMock.ethGetLogs(any()).send()).thenReturn(mockLogs)
+
+ val l1EventQuerier =
+ L1EventQuerierImpl(
+ vertx,
+ L1EventQuerierImpl.Config(
+ Duration.parse(pollingInterval),
+ maxEventScrapingTime,
+ earliestL1Block,
+ maxMessagesToAnchor,
+ testContractAddress,
+ "latest",
+ blockRangeLoopLimit
+ ),
+ l1ClientMock
+ )
+
+ l1EventQuerier.getSendMessageEventsForAnchoredMessage(null).thenApply {
+ testContext
+ .verify {
+ assertThat(it).isNotNull
+ assertThat(it).isEmpty()
+ }
+ .completeNow()
+ }
+ }
+
+ @Test
+ @Timeout(10, timeUnit = TimeUnit.SECONDS)
+ fun nullHashAndMoreThan100Messages_returns100Events(vertx: Vertx, testContext: VertxTestContext) {
+ val l1ClientMock = mock(defaultAnswer = Mockito.RETURNS_DEEP_STUBS)
+ val mockedEthBlock = mock(defaultAnswer = Mockito.RETURNS_DEEP_STUBS)
+ whenever(mockedEthBlock.block.number).thenReturn(BigInteger.valueOf(20))
+
+ whenever(l1ClientMock.ethGetBlockByNumber({ "latest" }, false).send())
+ .thenReturn(mockedEthBlock)
+
+ val mockLogs = mock()
+ val events = (blockInitialEventIsOn + 1..blockInitialEventIsOn + 106).map {
+ createRandomSendEvent(
+ it.toString(),
+ Random.nextInt().toString()
+ )
+ }
+ whenever(mockLogs.logs).thenReturn(events)
+ whenever(l1ClientMock.ethGetLogs(any()).send()).thenReturn(mockLogs)
+
+ val l1EventQuerier =
+ L1EventQuerierImpl(
+ vertx,
+ L1EventQuerierImpl.Config(
+ Duration.parse(pollingInterval),
+ maxEventScrapingTime,
+ earliestL1Block,
+ maxMessagesToAnchor,
+ testContractAddress,
+ "latest",
+ blockRangeLoopLimit
+ ),
+ l1ClientMock
+ )
+
+ l1EventQuerier.getSendMessageEventsForAnchoredMessage(null).thenApply {
+ testContext
+ .verify {
+ assertThat(it).isNotNull
+ assertThat(it.count()).isEqualTo(100)
+ }
+ .completeNow()
+ }
+ }
+
+ @Test
+ @Timeout(10, timeUnit = TimeUnit.SECONDS)
+ fun nullHashAndLessThan100Messages_returnsLessThan100Events(
+ vertx: Vertx,
+ testContext: VertxTestContext
+ ) {
+ val l1ClientMock = mock(defaultAnswer = Mockito.RETURNS_DEEP_STUBS)
+ val mockedEthBlock = mock(defaultAnswer = Mockito.RETURNS_DEEP_STUBS)
+
+ whenever(mockedEthBlock.block.number)
+ .thenReturn(BigInteger.valueOf(20))
+ .thenReturn(BigInteger.valueOf(100))
+ whenever(l1ClientMock.ethGetBlockByNumber({ "latest" }, false).send())
+ .thenReturn(mockedEthBlock)
+
+ val mockLogs = mock()
+ val events = (blockInitialEventIsOn + 1..blockInitialEventIsOn + 80).map {
+ createRandomSendEvent(
+ it.toString(),
+ Random.nextInt().toString()
+ )
+ }
+ whenever(mockLogs.logs).thenReturn(events)
+
+ whenever(l1ClientMock.ethGetLogs(any()).send()).thenReturn(mockLogs).thenReturn(mock())
+
+ val l1EventQuerier =
+ L1EventQuerierImpl(
+ vertx,
+ L1EventQuerierImpl.Config(
+ Duration.parse(pollingInterval),
+ maxEventScrapingTime,
+ earliestL1Block,
+ maxMessagesToAnchor,
+ testContractAddress,
+ "latest",
+ blockRangeLoopLimit
+ ),
+ l1ClientMock
+ )
+
+ l1EventQuerier.getSendMessageEventsForAnchoredMessage(null).thenApply {
+ testContext
+ .verify {
+ assertThat(it).isNotNull
+ assertThat(it.count()).isEqualTo(80)
+ }
+ .completeNow()
+ }
+ }
+
+ @Test
+ @Timeout(10, timeUnit = TimeUnit.SECONDS)
+ fun nullHashAndMoreThan100MessagesInMultipleQueries_returns100Events(
+ vertx: Vertx,
+ testContext: VertxTestContext
+ ) {
+ val l1ClientMock = mock(defaultAnswer = Mockito.RETURNS_DEEP_STUBS)
+ val mockedEthBlock = mock(defaultAnswer = Mockito.RETURNS_DEEP_STUBS)
+
+ whenever(mockedEthBlock.block.number)
+ .thenReturn(BigInteger.valueOf(20))
+ .thenReturn(BigInteger.valueOf(140))
+ whenever(l1ClientMock.ethGetBlockByNumber({ "latest" }, false).send())
+ .thenReturn(mockedEthBlock)
+
+ val mockLogs = mock()
+ val events = (blockInitialEventIsOn + 1..blockInitialEventIsOn + 80).map {
+ createRandomSendEvent(
+ it.toString(),
+ Random.nextInt().toString()
+ )
+ }
+ whenever(mockLogs.logs).thenReturn(events)
+
+ val mockLogsRound2 = mock()
+ val eventsRound2 = (blockInitialEventIsOn + 100..blockInitialEventIsOn + 120).map {
+ createRandomSendEvent(
+ it.toString(),
+ Random.nextInt().toString()
+ )
+ }
+ whenever(mockLogsRound2.logs).thenReturn(eventsRound2)
+
+ whenever(l1ClientMock.ethGetLogs(any()).send()).thenReturn(mockLogs).thenReturn(mockLogsRound2)
+
+ val l1EventQuerier =
+ L1EventQuerierImpl(
+ vertx,
+ L1EventQuerierImpl.Config(
+ Duration.parse(pollingInterval),
+ maxEventScrapingTime,
+ earliestL1Block,
+ maxMessagesToAnchor,
+ testContractAddress,
+ "latest",
+ blockRangeLoopLimit
+ ),
+ l1ClientMock
+ )
+
+ l1EventQuerier.getSendMessageEventsForAnchoredMessage(null).thenApply {
+ testContext
+ .verify {
+ assertThat(it).isNotNull
+ assertThat(it.count()).isEqualTo(100)
+ }
+ .completeNow()
+ }
+ }
+
+ @Test
+ @Timeout(10, timeUnit = TimeUnit.SECONDS)
+ fun existingHashNotFoundAndNoMessages_returnsNoEvents(
+ vertx: Vertx,
+ testContext: VertxTestContext
+ ) {
+ val l1ClientMock = mock(defaultAnswer = Mockito.RETURNS_DEEP_STUBS)
+ val mockedEthBlock = mock(defaultAnswer = Mockito.RETURNS_DEEP_STUBS)
+ whenever(mockedEthBlock.block.number).thenReturn(BigInteger.valueOf(20))
+
+ whenever(l1ClientMock.ethGetBlockByNumber(any(), any()).send())
+ .thenReturn(mockedEthBlock)
+
+ val emptyEvents: List> = listOf()
+ val mockLogs = mock()
+ whenever(mockLogs.logs).thenReturn(emptyEvents)
+ whenever(l1ClientMock.ethGetLogs(any()).send()).thenReturn(mockLogs)
+
+ val l1EventQuerier =
+ L1EventQuerierImpl(
+ vertx,
+ L1EventQuerierImpl.Config(
+ Duration.parse(pollingInterval),
+ maxEventScrapingTime,
+ earliestL1Block,
+ maxMessagesToAnchor,
+ testContractAddress,
+ "latest",
+ blockRangeLoopLimit
+ ),
+ l1ClientMock
+ )
+
+ l1EventQuerier
+ .getSendMessageEventsForAnchoredMessage(
+ MessageHashAnchoredEvent(messageHash = Bytes32.random())
+ )
+ .thenApply {
+ testContext
+ .verify {
+ assertThat(it).isNotNull
+ assertThat(it).isEmpty()
+ }
+ .completeNow()
+ }
+ }
+
+ @Test
+ @Timeout(10, timeUnit = TimeUnit.SECONDS)
+ fun existingHashFoundAndNoMessages_returnsNoEvents(vertx: Vertx, testContext: VertxTestContext) {
+ val messageHash = Bytes32.random()
+ val l1ClientMock = mock(defaultAnswer = Mockito.RETURNS_DEEP_STUBS)
+ val mockedEthBlock = mock(defaultAnswer = Mockito.RETURNS_DEEP_STUBS)
+ whenever(mockedEthBlock.block.number)
+ .thenReturn(BigInteger.valueOf(20))
+ .thenReturn(BigInteger.valueOf(30))
+
+ whenever(l1ClientMock.ethGetBlockByNumber({ "latest" }, false).send())
+ .thenReturn(mockedEthBlock)
+
+ val mockLogs = mock()
+ val emptyEvents: List> = listOf()
+ whenever(mockLogs.logs).thenReturn(emptyEvents)
+ whenever(l1ClientMock.ethGetLogs(any()).send()).thenReturn(mockLogs)
+
+ val eventMockLogs = mock()
+ val initialEvent = createRandomSendEvent(blockInitialEventIsOn.toString(), Random.nextInt().toString())
+ whenever(eventMockLogs.logs).thenReturn(listOf(initialEvent))
+ whenever(l1ClientMock.ethGetLogs(buildMessageHashEventFilter(messageHash)).send()).thenReturn(eventMockLogs)
+ .thenReturn(mockLogs)
+
+ val l1EventQuerier =
+ L1EventQuerierImpl(
+ vertx,
+ L1EventQuerierImpl.Config(
+ Duration.parse(pollingInterval),
+ maxEventScrapingTime,
+ earliestL1Block,
+ maxMessagesToAnchor,
+ testContractAddress,
+ "latest",
+ blockRangeLoopLimit
+ ),
+ l1ClientMock
+ )
+
+ l1EventQuerier
+ .getSendMessageEventsForAnchoredMessage(MessageHashAnchoredEvent(messageHash))
+ .thenApply {
+ testContext
+ .verify {
+ assertThat(it).isNotNull
+ assertThat(it).isEmpty()
+ }
+ .completeNow()
+ }
+ }
+
+ @Test
+ @Timeout(10, timeUnit = TimeUnit.SECONDS)
+ fun existingHashFoundMoreThan100Messages_returns100Events(
+ vertx: Vertx,
+ testContext: VertxTestContext
+ ) {
+ val messageHash = Bytes32.random()
+ val l1ClientMock = mock(defaultAnswer = Mockito.RETURNS_DEEP_STUBS)
+ val mockedEthBlock = mock(defaultAnswer = Mockito.RETURNS_DEEP_STUBS)
+ whenever(mockedEthBlock.block.number)
+ .thenReturn(BigInteger.valueOf(20))
+ .thenReturn(BigInteger.valueOf(30))
+
+ whenever(l1ClientMock.ethGetBlockByNumber({ "latest" }, false).send())
+ .thenReturn(mockedEthBlock)
+
+ val mockLogs = mock()
+ val newEvents = (blockInitialEventIsOn + 1..blockInitialEventIsOn + 100)
+ .map { createRandomSendEvent(it.toString(), Random.nextInt().toString()) }
+ whenever(mockLogs.logs).thenReturn(newEvents)
+ whenever(l1ClientMock.ethGetLogs(any()).send()).thenReturn(mockLogs)
+
+ val eventMockLogs = mock()
+ val initialEvent = createRandomSendEvent(blockInitialEventIsOn.toString(), Random.nextInt().toString())
+ whenever(eventMockLogs.logs).thenReturn(listOf(initialEvent))
+ whenever(l1ClientMock.ethGetLogs(buildMessageHashEventFilter(messageHash)).send()).thenReturn(eventMockLogs)
+ .thenReturn(mockLogs)
+
+ val l1EventQuerier =
+ L1EventQuerierImpl(
+ vertx,
+ L1EventQuerierImpl.Config(
+ Duration.parse(pollingInterval),
+ maxEventScrapingTime,
+ earliestL1Block,
+ maxMessagesToAnchor,
+ testContractAddress,
+ "latest",
+ blockRangeLoopLimit
+ ),
+ l1ClientMock
+ )
+
+ l1EventQuerier
+ .getSendMessageEventsForAnchoredMessage(MessageHashAnchoredEvent(messageHash))
+ .thenApply {
+ testContext
+ .verify {
+ assertThat(it).isNotNull
+ assertThat(it.count()).isEqualTo(100)
+ }
+ .completeNow()
+ }
+ }
+
+ @Test
+ @Timeout(10, timeUnit = TimeUnit.SECONDS)
+ fun existingHashFoundLessThan100Messages_returnsLessThan100Events(
+ vertx: Vertx,
+ testContext: VertxTestContext
+ ) {
+ val messageHash = Bytes32.random()
+ val l1ClientMock = mock(defaultAnswer = Mockito.RETURNS_DEEP_STUBS)
+ val mockedEthBlock = mock(defaultAnswer = Mockito.RETURNS_DEEP_STUBS)
+ whenever(mockedEthBlock.block.number)
+ .thenReturn(BigInteger.valueOf(20))
+ .thenReturn(BigInteger.valueOf(30))
+
+ whenever(l1ClientMock.ethGetBlockByNumber({ "latest" }, false).send())
+ .thenReturn(mockedEthBlock)
+
+ val mockLogs = mock()
+ val newEvents = (blockInitialEventIsOn + 1..blockInitialEventIsOn + 80).map {
+ createRandomSendEvent(
+ it.toString(),
+ Random.nextInt().toString()
+ )
+ }
+ whenever(mockLogs.logs).thenReturn(newEvents)
+ whenever(l1ClientMock.ethGetLogs(any()).send()).thenReturn(mockLogs)
+
+ val emptyLogs = mock()
+ val emptyEvents: List> = listOf()
+ whenever(emptyLogs.logs).thenReturn(emptyEvents)
+ whenever(l1ClientMock.ethGetLogs(any()).send()).thenReturn(emptyLogs)
+
+ val eventMockLogs = mock()
+ val initialEvent = createRandomSendEvent(blockInitialEventIsOn.toString(), Random.nextInt().toString())
+ whenever(eventMockLogs.logs).thenReturn(listOf(initialEvent))
+ whenever(l1ClientMock.ethGetLogs(buildMessageHashEventFilter(messageHash)).send())
+ .thenReturn(eventMockLogs)
+ .thenReturn(mockLogs).thenReturn(emptyLogs)
+
+ val l1EventQuerier =
+ L1EventQuerierImpl(
+ vertx,
+ L1EventQuerierImpl.Config(
+ Duration.parse(pollingInterval),
+ maxEventScrapingTime,
+ earliestL1Block,
+ maxMessagesToAnchor,
+ testContractAddress,
+ "latest",
+ blockRangeLoopLimit
+ ),
+ l1ClientMock
+ )
+
+ l1EventQuerier
+ .getSendMessageEventsForAnchoredMessage(MessageHashAnchoredEvent(messageHash))
+ .thenApply {
+ testContext
+ .verify {
+ assertThat(it).isNotNull
+ assertThat(it.count()).isEqualTo(80)
+ }
+ .completeNow()
+ }
+ }
+
+ @Test
+ @Timeout(10, timeUnit = TimeUnit.SECONDS)
+ fun existingHashFound_DoesNotReturnDuplicateHashesWhenFinalBlockIsAlwaysTheSame(
+ vertx: Vertx,
+ testContext: VertxTestContext
+ ) {
+ val startingIndexForEvents = 1
+ val expectedEventCount = 20
+ val finalBlockThatDoesNotChange = blockInitialEventIsOn + 1
+ val messageHash = Bytes32.random()
+ val l1ClientMock = mock(defaultAnswer = Mockito.RETURNS_DEEP_STUBS)
+
+ whenever(l1ClientMock.ethGetBlockByNumber(any(), any()).send().block.number)
+ .thenReturn(BigInteger.valueOf(finalBlockThatDoesNotChange.toLong()))
+
+ // all expected returned events, incrementing the log index
+ val mockLogs = mock()
+ val newEvents = (startingIndexForEvents..expectedEventCount).map {
+ createRandomSendEvent(
+ finalBlockThatDoesNotChange.toString(),
+ it.toString()
+ )
+ }
+ whenever(mockLogs.logs).thenReturn(newEvents)
+
+ val foundEventLog = mock()
+ val initialEvent = createRandomSendEvent(blockInitialEventIsOn.toString(), blockInitialEventIsOn.toString())
+ whenever(foundEventLog.logs).thenReturn(listOf(initialEvent))
+
+ whenever(l1ClientMock.ethGetLogs(any()).send())
+ .thenReturn(foundEventLog).thenReturn(mockLogs)
+
+ val l1EventQuerier =
+ L1EventQuerierImpl(
+ vertx,
+ L1EventQuerierImpl.Config(
+ Duration.parse(pollingInterval),
+ maxEventScrapingTime,
+ earliestL1Block,
+ maxMessagesToAnchor,
+ testContractAddress,
+ "latest",
+ blockRangeLoopLimit
+ ),
+ l1ClientMock
+ )
+
+ l1EventQuerier
+ .getSendMessageEventsForAnchoredMessage(MessageHashAnchoredEvent(messageHash))
+ .thenApply {
+ testContext
+ .verify {
+ assertThat(it).isNotNull
+ assertThat(it.count()).isEqualTo(expectedEventCount)
+ }
+ .completeNow()
+ }
+ }
+
+ @Test
+ @Timeout(10, timeUnit = TimeUnit.SECONDS)
+ fun existingHashFound_DoesNotReturnDuplicateHashesWhenFinalBlockIsTheSameRepeatedlyAndThenChanges(
+ vertx: Vertx,
+ testContext: VertxTestContext
+ ) {
+ val expectedCountOnFirstFinalizedBlock = 20
+ val expectedCountOnMovedOnFinalizedBlock = 20
+ val finalBlockThatIsTheSameMultipleTimes = blockInitialEventIsOn + 1
+ val movedOnFinalBlock = finalBlockThatIsTheSameMultipleTimes + 1
+ val messageHash = Bytes32.random()
+ val l1ClientMock = mock(defaultAnswer = Mockito.RETURNS_DEEP_STUBS)
+ val startingIndexForEvents = 1
+
+ // return the same block multiple times, then move on
+ whenever(l1ClientMock.ethGetBlockByNumber(any(), any()).send().block.number)
+ .thenReturn(BigInteger.valueOf(finalBlockThatIsTheSameMultipleTimes.toLong()))
+ .thenReturn(BigInteger.valueOf(finalBlockThatIsTheSameMultipleTimes.toLong()))
+ .thenReturn(BigInteger.valueOf(finalBlockThatIsTheSameMultipleTimes.toLong()))
+ .thenReturn(BigInteger.valueOf(movedOnFinalBlock.toLong()))
+
+ // all expected returned events, incrementing the log index
+ val initialFinalizedBlockLogs = mock()
+ val newEvents = (startingIndexForEvents..expectedCountOnFirstFinalizedBlock).map {
+ createRandomSendEvent(
+ finalBlockThatIsTheSameMultipleTimes.toString(),
+ it.toString()
+ )
+ }
+ whenever(initialFinalizedBlockLogs.logs).thenReturn(newEvents)
+
+ val movedOnFinalizedLogs = mock()
+ val movedOnEvents = (startingIndexForEvents..expectedCountOnMovedOnFinalizedBlock).map {
+ createRandomSendEvent(
+ movedOnFinalBlock.toString(),
+ it.toString()
+ )
+ }
+ whenever(movedOnFinalizedLogs.logs).thenReturn(movedOnEvents)
+
+ val foundEventLog = mock()
+ val initialEvent = createRandomSendEvent(blockInitialEventIsOn.toString(), blockInitialEventIsOn.toString())
+ whenever(foundEventLog.logs).thenReturn(listOf(initialEvent))
+
+ // return the same data for the same returned block multiple times, then move on
+ whenever(l1ClientMock.ethGetLogs(any()).send())
+ .thenReturn(foundEventLog)
+ .thenReturn(initialFinalizedBlockLogs)
+ .thenReturn(initialFinalizedBlockLogs)
+ .thenReturn(initialFinalizedBlockLogs)
+ .thenReturn(movedOnFinalizedLogs)
+ .thenReturn(movedOnFinalizedLogs)
+
+ val l1EventQuerier =
+ L1EventQuerierImpl(
+ vertx,
+ L1EventQuerierImpl.Config(
+ Duration.parse(pollingInterval),
+ maxEventScrapingTime,
+ earliestL1Block,
+ maxMessagesToAnchor,
+ testContractAddress,
+ "latest",
+ blockRangeLoopLimit
+ ),
+ l1ClientMock
+ )
+
+ l1EventQuerier
+ .getSendMessageEventsForAnchoredMessage(MessageHashAnchoredEvent(messageHash))
+ .thenApply {
+ testContext
+ .verify {
+ assertThat(it).isNotNull
+ assertThat(it.count()).isEqualTo(expectedCountOnFirstFinalizedBlock + expectedCountOnMovedOnFinalizedBlock)
+ }
+ .completeNow()
+ }
+ }
+
+ @Test
+ @Timeout(10, timeUnit = TimeUnit.SECONDS)
+ fun existingHashFound_returnsEventsOnLaterBlocksWithLowerLogIndex(
+ vertx: Vertx,
+ testContext: VertxTestContext
+ ) {
+ val messageHash = Bytes32.random()
+ val l1ClientMock = mock(defaultAnswer = Mockito.RETURNS_DEEP_STUBS)
+ val mockedEthBlock = mock(defaultAnswer = Mockito.RETURNS_DEEP_STUBS)
+ whenever(mockedEthBlock.block.number)
+ .thenReturn(BigInteger.valueOf(20))
+ .thenReturn(BigInteger.valueOf(100))
+
+ whenever(l1ClientMock.ethGetBlockByNumber({ "latest" }, false).send())
+ .thenReturn(mockedEthBlock)
+
+ val mockLogs = mock()
+ val newEvents = (blockInitialEventIsOn + 1..blockInitialEventIsOn + 80).map {
+ createRandomSendEvent(
+ it.toString(),
+ it.toString() // enforcing a lower index
+ )
+ }
+ whenever(mockLogs.logs).thenReturn(newEvents)
+ whenever(l1ClientMock.ethGetLogs(any()).send()).thenReturn(mockLogs)
+
+ val emptyLogs = mock()
+ val emptyEvents: List> = listOf()
+ whenever(emptyLogs.logs).thenReturn(emptyEvents)
+ whenever(l1ClientMock.ethGetLogs(any()).send()).thenReturn(emptyLogs)
+
+ val eventMockLogs = mock()
+ // Zenhub 770 - Enforcing a higher log index for the initial block to validate later blocks return results
+ val initialEvent = createRandomSendEvent(blockInitialEventIsOn.toString(), "100") // all previous indexes are 20-99
+ whenever(eventMockLogs.logs).thenReturn(listOf(initialEvent))
+ whenever(l1ClientMock.ethGetLogs(buildMessageHashEventFilter(messageHash)).send())
+ .thenReturn(eventMockLogs)
+ .thenReturn(mockLogs).thenReturn(emptyLogs)
+
+ val l1EventQuerier =
+ L1EventQuerierImpl(
+ vertx,
+ L1EventQuerierImpl.Config(
+ Duration.parse(pollingInterval),
+ maxEventScrapingTime,
+ earliestL1Block,
+ maxMessagesToAnchor,
+ testContractAddress,
+ "latest",
+ blockRangeLoopLimit
+ ),
+ l1ClientMock
+ )
+
+ l1EventQuerier
+ .getSendMessageEventsForAnchoredMessage(MessageHashAnchoredEvent(messageHash))
+ .thenApply {
+ testContext
+ .verify {
+ assertThat(it).isNotNull
+ assertThat(it.count()).isEqualTo(80)
+ }
+ .completeNow()
+ }
+ }
+
+ @Test
+ @Timeout(10, timeUnit = TimeUnit.SECONDS)
+ fun existingHashFound_returnsEventsWithHigherLogIndexOnSameBlock(
+ vertx: Vertx,
+ testContext: VertxTestContext
+ ) {
+ val messageHash = Bytes32.random()
+ val l1ClientMock = mock(defaultAnswer = Mockito.RETURNS_DEEP_STUBS)
+ val mockedEthBlock = mock(defaultAnswer = Mockito.RETURNS_DEEP_STUBS)
+ whenever(mockedEthBlock.block.number)
+ .thenReturn(BigInteger.valueOf(20))
+ .thenReturn(BigInteger.valueOf(100))
+
+ whenever(l1ClientMock.ethGetBlockByNumber({ "latest" }, false).send())
+ .thenReturn(mockedEthBlock)
+
+ val sameBlockNumber = Random.nextInt()
+ val mockLogs = mock()
+ // have all the events in the same block
+ val newEvents = (logIndexStart + 1..logIndexStart + 100).map {
+ createRandomSendEvent(
+ sameBlockNumber.toString(),
+ it.toString()
+ )
+ }
+ whenever(mockLogs.logs).thenReturn(newEvents)
+ whenever(l1ClientMock.ethGetLogs(any()).send()).thenReturn(mockLogs)
+
+ val emptyLogs = mock()
+ val emptyEvents: List> = listOf()
+ whenever(emptyLogs.logs).thenReturn(emptyEvents)
+ whenever(l1ClientMock.ethGetLogs(any()).send()).thenReturn(emptyLogs)
+
+ val eventMockLogs = mock()
+ // Forcing a lower index on the same block
+ val initialEvent = createRandomSendEvent(sameBlockNumber.toString(), logIndexStart.toString())
+ whenever(eventMockLogs.logs).thenReturn(listOf(initialEvent))
+ whenever(l1ClientMock.ethGetLogs(buildMessageHashEventFilter(messageHash)).send())
+ .thenReturn(eventMockLogs)
+ .thenReturn(mockLogs).thenReturn(emptyLogs)
+
+ val l1EventQuerier =
+ L1EventQuerierImpl(
+ vertx,
+ L1EventQuerierImpl.Config(
+ Duration.parse(pollingInterval),
+ maxEventScrapingTime,
+ earliestL1Block,
+ maxMessagesToAnchor,
+ testContractAddress,
+ "latest",
+ blockRangeLoopLimit
+ ),
+ l1ClientMock
+ )
+
+ l1EventQuerier
+ .getSendMessageEventsForAnchoredMessage(MessageHashAnchoredEvent(messageHash))
+ .thenApply {
+ testContext
+ .verify {
+ assertThat(it).isNotNull
+ assertThat(it.count()).isEqualTo(100)
+ }
+ .completeNow()
+ }
+ }
+
+ @Test
+ @Timeout(10, timeUnit = TimeUnit.SECONDS)
+ fun foundHashAndMoreThan100MessagesInMultipleQueries_returns100Events(
+ vertx: Vertx,
+ testContext: VertxTestContext
+ ) {
+ val messageHash = Bytes32.random()
+ val l1ClientMock = mock(defaultAnswer = Mockito.RETURNS_DEEP_STUBS)
+ val mockedEthBlock = mock(defaultAnswer = Mockito.RETURNS_DEEP_STUBS)
+ whenever(mockedEthBlock.block.number)
+ .thenReturn(BigInteger.valueOf(20))
+ .thenReturn(BigInteger.valueOf(130))
+
+ whenever(l1ClientMock.ethGetBlockByNumber({ "latest" }, false).send())
+ .thenReturn(mockedEthBlock)
+
+ val mockLogs = mock()
+ val newEvents = (blockInitialEventIsOn + 1..blockInitialEventIsOn + 80).map {
+ createRandomSendEvent(
+ it.toString(),
+ Random.nextInt().toString()
+ )
+ }
+ whenever(mockLogs.logs).thenReturn(newEvents)
+
+ val mockLogsRound2 = mock()
+ val newEventsRound2 = (blockInitialEventIsOn + 100..blockInitialEventIsOn + 120).map {
+ createRandomSendEvent(
+ it.toString(),
+ Random.nextInt().toString()
+ )
+ }
+ whenever(mockLogsRound2.logs).thenReturn(newEventsRound2)
+ whenever(l1ClientMock.ethGetLogs(any()).send()).thenReturn(mockLogsRound2)
+
+ val eventMockLogs = mock()
+ val events = createRandomSendEvent(blockInitialEventIsOn.toString(), Random.nextInt().toString())
+ whenever(eventMockLogs.logs).thenReturn(listOf(events))
+ whenever(l1ClientMock.ethGetLogs(buildMessageHashEventFilter(messageHash)).send()).thenReturn(eventMockLogs)
+ .thenReturn(mockLogs).thenReturn(mockLogsRound2)
+
+ val l1EventQuerier =
+ L1EventQuerierImpl(
+ vertx,
+ L1EventQuerierImpl.Config(
+ Duration.parse(pollingInterval),
+ maxEventScrapingTime,
+ earliestL1Block,
+ maxMessagesToAnchor,
+ testContractAddress,
+ "latest",
+ blockRangeLoopLimit
+ ),
+ l1ClientMock
+ )
+
+ l1EventQuerier
+ .getSendMessageEventsForAnchoredMessage(MessageHashAnchoredEvent(messageHash))
+ .thenApply {
+ testContext
+ .verify {
+ assertThat(it).isNotNull
+ assertThat(it.count()).isEqualTo(100)
+ }
+ .completeNow()
+ }
+ }
+
+ private fun createRandomSendEvent(blockNumber: String, logIndex: String): LogResult {
+ val log = Log()
+ val eventSignature: String = EventEncoder.encode(ZkEvmV2.MESSAGESENT_EVENT)
+ val messageHashValue = Bytes32.random()
+ val messageHash = org.web3j.abi.datatypes.generated.Bytes32(messageHashValue.toArray())
+
+ log.topics =
+ listOf(
+ eventSignature,
+ TypeEncoder.encode(testFromAddress),
+ TypeEncoder.encode(testToAddress),
+ TypeEncoder.encode(messageHash)
+ )
+
+ log.data =
+ FunctionEncoder.encodeConstructor(
+ listOf(
+ testFee,
+ testValue,
+ testNonce,
+ callData
+ )
+ )
+
+ log.setBlockNumber(blockNumber)
+ log.setLogIndex(logIndex)
+
+ return LogResult { log }
+ }
+
+ private fun buildMessageHashEventFilter(messageHash: Bytes32): EthFilter {
+ val messageHashFilter =
+ EthFilter(
+ DefaultBlockParameter.valueOf(earliestL1Block),
+ DefaultBlockParameter.valueOf(BigInteger.valueOf(20)),
+ testContractAddress
+ )
+
+ messageHashFilter.addOptionalTopics(messageHash.toString())
+
+ return messageHashFilter
+ }
+}
diff --git a/coordinator/app/src/test/kotlin/net/consensys/zkevm/ethereum/coordination/messageanchoring/L2MessageAnchorerImplTest.kt b/coordinator/app/src/test/kotlin/net/consensys/zkevm/ethereum/coordination/messageanchoring/L2MessageAnchorerImplTest.kt
new file mode 100644
index 000000000..ee3d20ce9
--- /dev/null
+++ b/coordinator/app/src/test/kotlin/net/consensys/zkevm/ethereum/coordination/messageanchoring/L2MessageAnchorerImplTest.kt
@@ -0,0 +1,176 @@
+package net.consensys.zkevm.ethereum.coordination.messageanchoring
+
+import io.vertx.core.Vertx
+import io.vertx.junit5.Timeout
+import io.vertx.junit5.VertxExtension
+import io.vertx.junit5.VertxTestContext
+import net.consensys.linea.contract.L2MessageService
+import net.consensys.linea.toHexString
+import net.consensys.linea.toULong
+import net.consensys.zkevm.ethereum.EIP1559GasProvider
+import net.consensys.zkevm.ethereum.crypto.ECKeypairSignerAdapter
+import net.consensys.zkevm.ethereum.crypto.Signer
+import org.apache.tuweni.bytes.Bytes
+import org.apache.tuweni.bytes.Bytes32
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.RepeatedTest
+import org.junit.jupiter.api.extension.ExtendWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+import org.web3j.crypto.Credentials
+import org.web3j.crypto.ECKeyPair
+import org.web3j.crypto.Hash
+import org.web3j.crypto.Keys
+import org.web3j.protocol.Web3j
+import org.web3j.protocol.core.DefaultBlockParameter
+import org.web3j.protocol.core.Request
+import org.web3j.protocol.core.methods.response.EthBlock
+import org.web3j.protocol.core.methods.response.EthFeeHistory
+import org.web3j.protocol.core.methods.response.EthGasPrice
+import org.web3j.protocol.core.methods.response.EthSendTransaction
+import org.web3j.protocol.core.methods.response.TransactionReceipt
+import tech.pegasys.teku.infrastructure.async.SafeFuture
+import java.math.BigInteger
+import java.util.*
+import java.util.concurrent.TimeUnit
+import kotlin.time.Duration.Companion.milliseconds
+
+@ExtendWith(VertxExtension::class)
+class L2MessageAnchorerImplTest {
+ private val testContractAddress = "0x6d976c9b8ceee705d4fe8699b44e5eb58242f484"
+ private val latestBlockNumber = 12345
+ private val transactionBlockNumber = 12340
+ private val keyPair = Keys.createEcKeyPair()
+ private val signer = ECKeypairSigner(keyPair)
+ private val pollingInterval = 10.milliseconds
+ private val gasEstimationPercentile = 0.5
+ private val gasLimit = BigInteger.valueOf(100)
+ private val feeHistoryBlockCount = 4u
+ private val maxFeePerGas = BigInteger.valueOf(10000)
+ private val retryCount = 10u
+ private val finalisedBlockDistance = latestBlockNumber.minus(transactionBlockNumber).toLong()
+
+ private val txHash = "0xfa41235fcc064e57ab2566d65732a25a24b36ff6edba3cdd5eb482071b435906"
+
+ @RepeatedTest(10)
+ @Timeout(10, timeUnit = TimeUnit.SECONDS)
+ fun messageAnchoring_returnsTransactionReceipt(vertx: Vertx, testContext: VertxTestContext) {
+ val mockReceipt = mock()
+ whenever(mockReceipt.isStatusOK).thenReturn(true)
+ whenever(mockReceipt.transactionHash).thenReturn(txHash)
+ whenever(mockReceipt.blockNumber).thenReturn(BigInteger.valueOf(transactionBlockNumber.toLong()))
+
+ val l1ClientMock = createMockedWeb3jClient(mockReceipt, transactionBlockNumber, latestBlockNumber, 31648428)
+ val l2ClientMock = createMockedWeb3jClient(mockReceipt, transactionBlockNumber, latestBlockNumber, 1337)
+ val messageManager = createL2MessageServiceContractWithSimpleKeypairSigner(l1ClientMock, l2ClientMock)
+ val testHashes = createRandomHashes(11)
+
+ val l2MessageAnchorerImpl =
+ L2MessageAnchorerImpl(
+ vertx,
+ l2ClientMock,
+ messageManager,
+ L2MessageAnchorerImpl.Config(pollingInterval, retryCount, finalisedBlockDistance)
+ )
+
+ l2MessageAnchorerImpl.anchorMessages(testHashes)
+ .thenApply {
+ testContext
+ .verify {
+ assertThat(it).isNotNull
+ assertThat(it.blockNumber).isEqualTo(mockReceipt.blockNumber)
+ assertThat(it.transactionHash).isEqualTo(mockReceipt.transactionHash)
+ }
+ .completeNow()
+ }
+ }
+
+ private fun createL2MessageServiceContractWithSimpleKeypairSigner(
+ l1Web3jClient: Web3j,
+ l2Web3jClient: Web3j
+ ): L2MessageService {
+ val signerAdapter = ECKeypairSignerAdapter(signer, keyPair.publicKey)
+ val credentials = Credentials.create(signerAdapter)
+ return L2MessageService.load(
+ testContractAddress,
+ l2Web3jClient,
+ credentials,
+ EIP1559GasProvider(
+ l2Web3jClient,
+ EIP1559GasProvider.Config(gasLimit, maxFeePerGas, feeHistoryBlockCount, gasEstimationPercentile)
+ )
+ )
+ }
+
+ private fun createRandomHashes(numberOfRandomHashes: Int): List {
+ return (0..numberOfRandomHashes)
+ .map { Bytes32.random() }
+ }
+
+ private fun createMockedWeb3jClient(
+ expectedTransactionReceipt: TransactionReceipt,
+ txBlockNumber: Int,
+ currentBlockNumber: Int,
+ chainId: Int
+ ): Web3j {
+ val web3jClient = mock(defaultAnswer = Mockito.RETURNS_DEEP_STUBS)
+ val ethBlock = mock()
+ val block = mock()
+ whenever(ethBlock.block).thenReturn(block)
+ whenever(block.number).thenReturn(BigInteger.valueOf(txBlockNumber.toLong()))
+ .thenReturn(BigInteger.valueOf(currentBlockNumber.toLong()))
+
+ whenever(web3jClient.ethGetBlockByNumber(any(), any()).sendAsync())
+ .thenAnswer { SafeFuture.completedFuture(ethBlock) }
+
+ whenever(
+ web3jClient
+ .ethFeeHistory(
+ ArgumentMatchers.eq(4),
+ ArgumentMatchers.eq(DefaultBlockParameter.valueOf("latest")),
+ ArgumentMatchers.eq(listOf(gasEstimationPercentile))
+ )
+ .sendAsync()
+ )
+ .thenAnswer {
+ val feeHistoryResponse = EthFeeHistory()
+ val feeHistory = EthFeeHistory.FeeHistory()
+ feeHistory.setReward(mutableListOf(mutableListOf("0x1000")))
+ feeHistory.setBaseFeePerGas(mutableListOf("0x100"))
+ feeHistory.setOldestBlock(BigInteger.valueOf(currentBlockNumber.toLong() - 1).toULong().toHexString())
+ feeHistoryResponse.result = feeHistory
+ SafeFuture.completedFuture(feeHistoryResponse)
+ }
+ whenever(web3jClient.ethGasPrice().sendAsync()).thenAnswer {
+ val gasPriceResponse = EthGasPrice()
+ gasPriceResponse.result = "0x100"
+ SafeFuture.completedFuture(gasPriceResponse)
+ }
+ val sendTransactionResponse = EthSendTransaction()
+ val expectedTransactionHash = txHash
+ sendTransactionResponse.result = expectedTransactionHash
+ whenever(web3jClient.ethSendRawTransaction(any())).thenAnswer {
+ val hashToReturn = Hash.sha3(it.arguments[0] as String)
+ sendTransactionResponse.result = hashToReturn
+ val requestMock = mock>()
+ whenever(requestMock.send()).thenReturn(sendTransactionResponse)
+ requestMock
+ }
+ whenever(web3jClient.ethGetTransactionReceipt(any()).send().transactionReceipt)
+ .thenReturn(Optional.of(expectedTransactionReceipt))
+ whenever(web3jClient.ethChainId().send().chainId)
+ .thenReturn(BigInteger.valueOf(chainId.toLong()))
+
+ return web3jClient
+ }
+
+ class ECKeypairSigner(private val keyPair: ECKeyPair) : Signer {
+ override fun sign(bytes: Bytes): Pair {
+ val signature = keyPair.sign(bytes.toArray())
+ return signature.r to signature.s
+ }
+ }
+}
diff --git a/coordinator/app/src/test/kotlin/net/consensys/zkevm/ethereum/coordination/messageanchoring/L2QuerierImplTest.kt b/coordinator/app/src/test/kotlin/net/consensys/zkevm/ethereum/coordination/messageanchoring/L2QuerierImplTest.kt
new file mode 100644
index 000000000..aa99f6e92
--- /dev/null
+++ b/coordinator/app/src/test/kotlin/net/consensys/zkevm/ethereum/coordination/messageanchoring/L2QuerierImplTest.kt
@@ -0,0 +1,198 @@
+package net.consensys.zkevm.ethereum.coordination.messageanchoring
+
+import io.vertx.core.Vertx
+import io.vertx.junit5.Timeout
+import io.vertx.junit5.VertxExtension
+import io.vertx.junit5.VertxTestContext
+import net.consensys.linea.contract.L2MessageService
+import net.consensys.linea.contract.L2MessageService.L1L2MESSAGEHASHESADDEDTOINBOX_EVENT
+import org.apache.tuweni.bytes.Bytes32
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.RepeatedTest
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.mockito.Mockito
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+import org.web3j.abi.EventEncoder
+import org.web3j.abi.FunctionEncoder
+import org.web3j.abi.FunctionReturnDecoder
+import org.web3j.abi.datatypes.DynamicArray
+import org.web3j.crypto.Credentials
+import org.web3j.crypto.Keys
+import org.web3j.protocol.Web3j
+import org.web3j.protocol.core.methods.response.EthBlockNumber
+import org.web3j.protocol.core.methods.response.EthCall
+import org.web3j.protocol.core.methods.response.EthLog
+import org.web3j.protocol.core.methods.response.Log
+import org.web3j.tx.gas.DefaultGasProvider
+import java.math.BigInteger
+import java.util.*
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.TimeUnit
+import kotlin.math.max
+
+@ExtendWith(VertxExtension::class)
+class L2QuerierImplTest {
+ private val testContractAddress = "0x6d976c9b8ceee705d4fe8699b44e5eb58242f484"
+ private val blockNumber = 13
+ private val keyPair = Keys.createEcKeyPair()
+
+ @RepeatedTest(10)
+ @Timeout(1, timeUnit = TimeUnit.SECONDS)
+ fun findLastFinalizedAnchoredEvent_returnsTheLastEvent(vertx: Vertx, testContext: VertxTestContext) {
+ val l2ClientMock = mock(defaultAnswer = Mockito.RETURNS_DEEP_STUBS)
+ whenever(l2ClientMock.ethBlockNumber().send().blockNumber)
+ .thenReturn(BigInteger.valueOf(blockNumber.toLong()))
+ val randomEvents =
+ listOf(
+ createRandomEventWithHashes(1),
+ createRandomEventWithHashes(2),
+ createRandomEventWithHashes(3)
+ )
+ val lastEventData = randomEvents.last().data
+ val expectedHash =
+ lastEventData.substring(lastEventData.length - Bytes32.ZERO.toUnprefixedHexString().length)
+
+ val mockLogs = mock()
+ val logResults: List> = randomEvents.map { EthLog.LogResult { it } }
+ whenever(mockLogs.logs).thenReturn(logResults)
+ whenever(l2ClientMock.ethGetLogs(any()).send()).thenReturn(mockLogs)
+
+ val credentials = Credentials.create(keyPair)
+ val messageManager =
+ L2MessageService.load(testContractAddress, l2ClientMock, credentials, DefaultGasProvider())
+ val l2Querier =
+ L2QuerierImpl(
+ l2ClientMock,
+ messageManager,
+ L2QuerierImpl.Config(1u, 1u, 1u, testContractAddress),
+ vertx
+ )
+ l2Querier.findLastFinalizedAnchoredEvent().thenApply {
+ testContext
+ .verify {
+ assertThat(it).isNotNull
+ assertThat(it!!.messageHash).isEqualTo(Bytes32.fromHexString(expectedHash))
+ }
+ .completeNow()
+ }
+ }
+
+ @RepeatedTest(10)
+ @Timeout(1, timeUnit = TimeUnit.SECONDS)
+ fun findLastFinalizedAnchoredEvent_isAbleToFindEventsInThePast(
+ vertx: Vertx,
+ testContext: VertxTestContext
+ ) {
+ val l2ClientMock = mock(defaultAnswer = Mockito.RETURNS_DEEP_STUBS)
+ whenever(l2ClientMock.ethBlockNumber().send().blockNumber)
+ .thenReturn(BigInteger.valueOf(blockNumber.toLong()))
+
+ val randomEventsForRequests = createRandomEventBatches(6, 4, 6)
+ val lastEventData = randomEventsForRequests.last().last().data
+
+ val expectedHash =
+ lastEventData.substring(lastEventData.length - Bytes32.ZERO.toUnprefixedHexString().length)
+
+ val mockLogs = mock()
+ val logResults: List> = randomEventsForRequests.last().map { EthLog.LogResult { it } }
+ whenever(mockLogs.logs).thenReturn(logResults)
+
+ val emptyEvents: List> = listOf()
+ val emptyMockLogs = mock()
+ whenever(emptyMockLogs.logs).thenReturn(emptyEvents)
+
+ whenever(l2ClientMock.ethGetLogs(any()).send()).thenAnswer {
+ emptyMockLogs
+ }.thenAnswer {
+ mockLogs
+ }
+
+ val credentials = Credentials.create(keyPair)
+ val messageManager =
+ L2MessageService.load(testContractAddress, l2ClientMock, credentials, DefaultGasProvider())
+
+ val l2Querier =
+ L2QuerierImpl(
+ l2ClientMock,
+ messageManager,
+ L2QuerierImpl.Config(1u, 5u, 10u, testContractAddress),
+ vertx
+ )
+ l2Querier.findLastFinalizedAnchoredEvent().thenApply {
+ testContext
+ .verify {
+ assertThat(it).isNotNull
+ assertThat(it!!.messageHash).isEqualTo(Bytes32.fromHexString(expectedHash))
+ }
+ .completeNow()
+ }
+ }
+
+ @Test
+ @Timeout(1, timeUnit = TimeUnit.SECONDS)
+ fun getMessageHashStatus(
+ vertx: Vertx,
+ testContext: VertxTestContext
+ ) {
+ val l2ClientMock = mock(defaultAnswer = Mockito.RETURNS_DEEP_STUBS)
+ val credentials = Credentials.create(keyPair)
+ val messageManager =
+ L2MessageService.load(testContractAddress, l2ClientMock, credentials, DefaultGasProvider())
+
+ val mockBlockNumberReturn = mock()
+ whenever(mockBlockNumberReturn.blockNumber).thenReturn(BigInteger.valueOf(blockNumber.toLong()))
+ whenever(l2ClientMock.ethBlockNumber().sendAsync())
+ .thenReturn(CompletableFuture.completedFuture(mockBlockNumberReturn))
+
+ val l2Querier =
+ L2QuerierImpl(
+ l2ClientMock,
+ messageManager,
+ L2QuerierImpl.Config(1u, 1u, 10u, testContractAddress),
+ vertx
+ )
+
+ val messageHash = Bytes32.random()
+ val mockEthCall = mock()
+ whenever(mockEthCall.value).thenReturn("0x0000000000000000000000000000000000000000000000000000000000000001")
+ whenever(l2ClientMock.ethCall(any(), any()).send()).thenReturn(mockEthCall)
+
+ l2Querier.getMessageHashStatus(messageHash).thenApply {
+ testContext
+ .verify {
+ assertThat(it).isNotNull
+ assertThat(it!!).isEqualTo(BigInteger.valueOf(1))
+ }
+ .completeNow()
+ }
+ }
+
+ private fun createRandomEventBatches(
+ numberOfBatches: Int,
+ maxEventsPerBatch: Int,
+ maxHashesPerEvent: Int
+ ): List> {
+ return (1..numberOfBatches).map {
+ val eventsToGenerate = max(Random().nextInt(maxEventsPerBatch), 1)
+ (1..eventsToGenerate).map { createRandomEventWithHashes(maxHashesPerEvent) }
+ }
+ }
+
+ private fun createRandomEventWithHashes(numberOfRandomHashes: Int): Log {
+ val log = Log()
+ val randomHashes =
+ (0..numberOfRandomHashes)
+ .map { Bytes32.random() }
+ .map { org.web3j.abi.datatypes.generated.Bytes32(it.toArray()) }
+ val eventSignature = EventEncoder.encode(L1L2MESSAGEHASHESADDEDTOINBOX_EVENT)
+
+ log.topics = listOf(eventSignature)
+ val data = DynamicArray(org.web3j.abi.datatypes.generated.Bytes32::class.java, randomHashes)
+ log.data = FunctionEncoder.encodeConstructor(listOf(data))
+ FunctionReturnDecoder.decode(log.data, L1L2MESSAGEHASHESADDEDTOINBOX_EVENT.nonIndexedParameters)
+ return log
+ }
+}
diff --git a/coordinator/app/src/test/kotlin/net/consensys/zkevm/ethereum/coordination/messageanchoring/MessageAnchoringServiceTest.kt b/coordinator/app/src/test/kotlin/net/consensys/zkevm/ethereum/coordination/messageanchoring/MessageAnchoringServiceTest.kt
new file mode 100644
index 000000000..3875c5171
--- /dev/null
+++ b/coordinator/app/src/test/kotlin/net/consensys/zkevm/ethereum/coordination/messageanchoring/MessageAnchoringServiceTest.kt
@@ -0,0 +1,182 @@
+package net.consensys.zkevm.ethereum.coordination.messageanchoring
+
+import io.vertx.core.Vertx
+import io.vertx.junit5.Timeout
+import io.vertx.junit5.VertxExtension
+import io.vertx.junit5.VertxTestContext
+import net.consensys.linea.contract.AsyncFriendlyTransactionManager
+import net.consensys.linea.contract.L2MessageService
+import org.apache.tuweni.bytes.Bytes32
+import org.assertj.core.api.Assertions
+import org.junit.jupiter.api.BeforeAll
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.TestInstance
+import org.junit.jupiter.api.extension.ExtendWith
+import org.mockito.Mockito.RETURNS_DEEP_STUBS
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.whenever
+import org.web3j.protocol.core.methods.response.TransactionReceipt
+import tech.pegasys.teku.infrastructure.async.SafeFuture
+import java.math.BigInteger
+import java.util.concurrent.TimeUnit
+import kotlin.time.Duration.Companion.milliseconds
+
+@ExtendWith(VertxExtension::class)
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+class MessageAnchoringServiceTest {
+ @BeforeAll
+ fun init() {
+ // To warmup assertions otherwise first test may fail
+ Assertions.assertThat(true).isTrue()
+ }
+
+ @Test
+ @Timeout(10, timeUnit = TimeUnit.SECONDS)
+ fun start_startsPollingProcess(vertx: Vertx, testContext: VertxTestContext) {
+ val pollingInterval = 10.milliseconds
+ val maxMessagesToAnchor = 100u
+
+ val l2MessageService = mock(defaultAnswer = RETURNS_DEEP_STUBS)
+ whenever(l2MessageService.INBOX_STATUS_UNKNOWN().send()).thenReturn(BigInteger.valueOf(0))
+
+ val mockTransactionManager = mock(defaultAnswer = RETURNS_DEEP_STUBS)
+ val mockL1Querier = mock(defaultAnswer = RETURNS_DEEP_STUBS)
+ val mockL2MessageAnchorer = mock(defaultAnswer = RETURNS_DEEP_STUBS)
+ val mockL2Querier = mock(defaultAnswer = RETURNS_DEEP_STUBS)
+
+ val foundAnchoredHashEvent = MessageHashAnchoredEvent(Bytes32.random())
+ val events = listOf(SendMessageEvent(Bytes32.random()))
+ val mockTransactionReceipt = mock()
+
+ whenever(mockL2Querier.findLastFinalizedAnchoredEvent()).thenReturn(
+ SafeFuture.completedFuture(foundAnchoredHashEvent)
+ )
+ whenever(mockL1Querier.getSendMessageEventsForAnchoredMessage(foundAnchoredHashEvent)).thenReturn(
+ SafeFuture.completedFuture(events)
+ )
+ whenever(mockL2Querier.getMessageHashStatus(events.first().messageHash)).thenReturn(
+ SafeFuture.completedFuture(BigInteger.valueOf(0))
+ )
+ whenever(mockL2MessageAnchorer.anchorMessages(events.map { it.messageHash })).thenReturn(
+ SafeFuture.completedFuture(mockTransactionReceipt)
+ )
+
+ whenever(mockTransactionReceipt.transactionHash).thenReturn(
+ Bytes32.random().toHexString()
+ )
+
+ whenever(mockTransactionManager.resetNonce()).thenAnswer { SafeFuture.completedFuture(Unit) }
+
+ val monitor =
+ MessageAnchoringService(
+ MessageAnchoringService.Config(
+ pollingInterval,
+ maxMessagesToAnchor
+ ),
+ vertx,
+ mockL1Querier,
+ mockL2MessageAnchorer,
+ mockL2Querier,
+ l2MessageService,
+ mockTransactionManager
+ )
+ monitor.start().thenApply {
+ vertx.setTimer(
+ 100
+ ) {
+ monitor.stop().thenApply {
+ testContext.verify {
+ verify(mockTransactionReceipt, atLeastOnce()).transactionHash
+ verify(mockL2Querier, atLeastOnce()).getMessageHashStatus(events.first().messageHash)
+ verify(mockL2Querier, atLeastOnce()).findLastFinalizedAnchoredEvent()
+ verify(mockL1Querier, atLeastOnce()).getSendMessageEventsForAnchoredMessage(foundAnchoredHashEvent)
+ verify(mockTransactionManager, atLeastOnce()).resetNonce()
+ }
+ .completeNow()
+ }
+ }
+ }
+ }
+
+ @Test
+ @Timeout(10, timeUnit = TimeUnit.SECONDS)
+ fun start_startsPollingProcessAndLimitsReturnedEvents(vertx: Vertx, testContext: VertxTestContext) {
+ val pollingInterval = 10.milliseconds
+ val maxMessagesToAnchor = 2u
+
+ val l2MessageService = mock(defaultAnswer = RETURNS_DEEP_STUBS)
+ whenever(l2MessageService.INBOX_STATUS_UNKNOWN().send()).thenReturn(BigInteger.valueOf(0))
+
+ val mockTransactionManager = mock(defaultAnswer = RETURNS_DEEP_STUBS)
+ val mockL1Querier = mock(defaultAnswer = RETURNS_DEEP_STUBS)
+ val mockL2MessageAnchorer = mock(defaultAnswer = RETURNS_DEEP_STUBS)
+ val mockL2Querier = mock(defaultAnswer = RETURNS_DEEP_STUBS)
+
+ val foundAnchoredHashEvent = MessageHashAnchoredEvent(Bytes32.random())
+ val events = listOf(
+ SendMessageEvent(Bytes32.random()),
+ SendMessageEvent(Bytes32.random()),
+ SendMessageEvent(Bytes32.random()),
+ SendMessageEvent(Bytes32.random())
+ )
+
+ val mockTransactionReceipt = mock()
+
+ whenever(mockL2Querier.findLastFinalizedAnchoredEvent()).thenReturn(
+ SafeFuture.completedFuture(foundAnchoredHashEvent)
+ )
+ whenever(mockL1Querier.getSendMessageEventsForAnchoredMessage(foundAnchoredHashEvent)).thenReturn(
+ SafeFuture.completedFuture(events)
+ )
+ whenever(mockL2Querier.getMessageHashStatus(any())).thenReturn(
+ SafeFuture.completedFuture(BigInteger.valueOf(0))
+ )
+ whenever(mockL2MessageAnchorer.anchorMessages(any())).thenReturn(
+ SafeFuture.completedFuture(mockTransactionReceipt)
+ )
+ whenever(mockTransactionReceipt.transactionHash).thenReturn(
+ Bytes32.random().toHexString()
+ )
+
+ whenever(mockTransactionManager.resetNonce()).thenAnswer { SafeFuture.completedFuture(Unit) }
+
+ val monitor =
+ MessageAnchoringService(
+ MessageAnchoringService.Config(
+ pollingInterval,
+ maxMessagesToAnchor
+ ),
+ vertx,
+ mockL1Querier,
+ mockL2MessageAnchorer,
+ mockL2Querier,
+ l2MessageService,
+ mockTransactionManager
+ )
+ monitor.start().thenApply {
+ vertx.setTimer(
+ 100
+ ) {
+ monitor.stop().thenApply {
+ testContext.verify {
+ verify(mockL2MessageAnchorer, atLeastOnce()).anchorMessages(events.take(2).map { it.messageHash })
+ verify(mockL2MessageAnchorer, never()).anchorMessages(events.take(4).map { it.messageHash })
+ verify(mockL2Querier, atLeastOnce()).getMessageHashStatus(events.first().messageHash)
+ verify(mockL2Querier, atLeastOnce()).getMessageHashStatus(events[1].messageHash)
+ verify(mockL2Querier, atLeastOnce()).getMessageHashStatus(events[2].messageHash)
+ verify(mockL2Querier, atLeastOnce()).getMessageHashStatus(events[3].messageHash)
+ verify(mockL2Querier, atLeastOnce()).findLastFinalizedAnchoredEvent()
+ verify(mockL1Querier, atLeastOnce()).getSendMessageEventsForAnchoredMessage(foundAnchoredHashEvent)
+ verify(mockTransactionReceipt, atLeastOnce()).transactionHash
+ verify(mockTransactionManager, atLeastOnce()).resetNonce()
+ }
+ .completeNow()
+ }
+ }
+ }
+ }
+}
diff --git a/coordinator/app/src/test/resources/log4j2.xml b/coordinator/app/src/test/resources/log4j2.xml
new file mode 100644
index 000000000..c284d1d2f
--- /dev/null
+++ b/coordinator/app/src/test/resources/log4j2.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/coordinator/app/src/test/resources/vertx-options.json b/coordinator/app/src/test/resources/vertx-options.json
new file mode 100644
index 000000000..80e459566
--- /dev/null
+++ b/coordinator/app/src/test/resources/vertx-options.json
@@ -0,0 +1,8 @@
+{
+ "blockedThreadCheckInterval" : 5,
+ "blockedThreadCheckIntervalUnit" : "MINUTES",
+ "maxEventLoopExecuteTime" : 2,
+ "maxEventLoopExecuteTimeUnit" : "MINUTES",
+ "maxWorkerExecuteTime": 5,
+ "maxWorkerExecuteTimeUnit": "MINUTES"
+}
diff --git a/coordinator/clients/engine-api-client/build.gradle b/coordinator/clients/engine-api-client/build.gradle
new file mode 100644
index 000000000..fbc4e389e
--- /dev/null
+++ b/coordinator/clients/engine-api-client/build.gradle
@@ -0,0 +1,9 @@
+plugins {
+ id 'net.consensys.zkevm.kotlin-library-conventions'
+}
+
+dependencies {
+ implementation project(':coordinator:core')
+ api "tech.pegasys.teku.internal:executionclient:${versions.teku}"
+ api "tech.pegasys.teku.internal:bytes:${versions.teku}"
+}
diff --git a/coordinator/clients/engine-api-client/src/main/kotlin/net/consensys/zkevm/ethereum/executionclient/TekuImplBasedEngineClient.kt b/coordinator/clients/engine-api-client/src/main/kotlin/net/consensys/zkevm/ethereum/executionclient/TekuImplBasedEngineClient.kt
new file mode 100644
index 000000000..d625bed30
--- /dev/null
+++ b/coordinator/clients/engine-api-client/src/main/kotlin/net/consensys/zkevm/ethereum/executionclient/TekuImplBasedEngineClient.kt
@@ -0,0 +1,49 @@
+package net.consensys.zkevm.ethereum.executionclient
+
+import com.github.michaelbull.result.Err
+import com.github.michaelbull.result.Ok
+import com.github.michaelbull.result.Result
+import net.consensys.linea.errors.ErrorResponse
+import tech.pegasys.teku.ethereum.executionclient.ExecutionEngineClient
+import tech.pegasys.teku.ethereum.executionclient.schema.ExecutionPayloadV1
+import tech.pegasys.teku.ethereum.executionclient.schema.ForkChoiceStateV1
+import tech.pegasys.teku.ethereum.executionclient.schema.ForkChoiceUpdatedResult
+import tech.pegasys.teku.ethereum.executionclient.schema.PayloadAttributesV1
+import tech.pegasys.teku.ethereum.executionclient.schema.PayloadStatusV1
+import tech.pegasys.teku.infrastructure.async.SafeFuture
+import tech.pegasys.teku.infrastructure.bytes.Bytes8
+import java.util.Optional
+
+class TekuImplBasedEngineClient(private val delegate: ExecutionEngineClient) :
+ net.consensys.zkevm.ethereum.executionclient.ExecutionEngineClient {
+ override fun getPayloadV1(
+ payloadId: Bytes8
+ ): SafeFuture>> {
+ return delegate.getPayloadV1(payloadId).thenApply(this::unwrapResponse)
+ }
+
+ override fun newPayloadV1(
+ executionPayload: ExecutionPayloadV1
+ ): SafeFuture>> {
+ return delegate.newPayloadV1(executionPayload).thenApply(this::unwrapResponse)
+ }
+
+ override fun forkChoiceUpdatedV1(
+ forkChoiceState: ForkChoiceStateV1,
+ payloadAttributes: PayloadAttributesV1?
+ ): SafeFuture>> {
+ return delegate
+ .forkChoiceUpdatedV1(forkChoiceState, Optional.ofNullable(payloadAttributes))
+ .thenApply(this::unwrapResponse)
+ }
+
+ private fun unwrapResponse(
+ response: tech.pegasys.teku.ethereum.executionclient.schema.Response
+ ): Result> {
+ return if (response.isFailure) {
+ Err(ErrorResponse(EngineErrorType.Unknown, response.errorMessage))
+ } else {
+ Ok(response.payload)
+ }
+ }
+}
diff --git a/coordinator/clients/persistence/build.gradle b/coordinator/clients/persistence/build.gradle
new file mode 100644
index 000000000..2ff0d4608
--- /dev/null
+++ b/coordinator/clients/persistence/build.gradle
@@ -0,0 +1,15 @@
+plugins {
+ id 'net.consensys.zkevm.kotlin-library-conventions'
+}
+
+dependencies {
+ implementation project(':coordinator:core')
+ implementation project(':jvm-libs:metrics:monitoring')
+ implementation project(':jvm-libs:future-extensions')
+ implementation "tech.pegasys.teku.internal:executionclient:${versions.teku}"
+
+ implementation "io.vertx:vertx-core:${versions.vertx}"
+ implementation "io.vertx:vertx-lang-kotlin:${versions.vertx}"
+
+ testImplementation "io.vertx:vertx-junit5:${versions.vertx}"
+}
diff --git a/coordinator/clients/persistence/src/main/kotlin/net/consensys/zkevm/coordinator/clients/FakeBlocksStore.kt b/coordinator/clients/persistence/src/main/kotlin/net/consensys/zkevm/coordinator/clients/FakeBlocksStore.kt
new file mode 100644
index 000000000..f1a1d19c2
--- /dev/null
+++ b/coordinator/clients/persistence/src/main/kotlin/net/consensys/zkevm/coordinator/clients/FakeBlocksStore.kt
@@ -0,0 +1,18 @@
+package net.consensys.zkevm.coordinator.clients
+
+import org.apache.tuweni.units.bigints.UInt64
+import tech.pegasys.teku.infrastructure.async.SafeFuture
+
+class FakeBlocksStore() : BlocksStore {
+ override fun saveBlock(block: T): SafeFuture<*> {
+ return SafeFuture.completedFuture(null)
+ }
+
+ override fun findBlockByHeight(blockNumber: UInt64): SafeFuture