diff --git a/.github/actions/build-evm/action.yaml b/.github/actions/build-evm/action.yaml index 269e1883be..833628d2bd 100644 --- a/.github/actions/build-evm/action.yaml +++ b/.github/actions/build-evm/action.yaml @@ -8,31 +8,41 @@ inputs: outputs: impl: description: "Implementation of EVM binary to build" - value: ${{ steps.evm-config-reader.outputs.impl }} + value: ${{ steps.config-evm-reader.outputs.impl }} repo: description: "Repository to use to build the EVM binary" - value: ${{ steps.evm-config-reader.outputs.repo }} + value: ${{ steps.config-evm-reader.outputs.repo }} ref: description: "Reference to branch, commit, or tag to use to build the EVM binary" - value: ${{ steps.evm-config-reader.outputs.ref }} + value: ${{ steps.config-evm-reader.outputs.ref }} + evm-bin: + description: "Binary name of the evm tool to use" + value: ${{ steps.config-evm-reader.outputs.evm-bin }} runs: using: "composite" steps: - - name: Get the selected EVM version from the evm-config.yaml - id: evm-config-reader + - name: Get the selected EVM version from the configs/evm.yaml + id: config-evm-reader shell: bash run: | - awk "/^${{ inputs.type }}:/{flag=1; next} /^[[:alnum:]]/{flag=0} flag" ./evm-config.yaml \ + awk "/^${{ inputs.type }}:/{flag=1; next} /^[[:alnum:]]/{flag=0} flag" ./configs/evm.yaml \ | sed 's/ //g' | sed 's/:/=/g' >> "$GITHUB_OUTPUT" - name: Print Variables for the selected EVM type shell: bash run: | - echo "Implementation: ${{ steps.evm-config-reader.outputs.impl }}" - echo "Repository: ${{ steps.evm-config-reader.outputs.repo }}" - echo "Reference: ${{ steps.evm-config-reader.outputs.ref }}" + echo "Implementation: ${{ steps.config-evm-reader.outputs.impl }}" + echo "Repository: ${{ steps.config-evm-reader.outputs.repo }}" + echo "Reference: ${{ steps.config-evm-reader.outputs.ref }}" + echo "EVM Binary: ${{ steps.config-evm-reader.outputs.evm-bin }}" - name: Build the EVM using Geth action - if: steps.evm-config-reader.outputs.impl == 'geth' + if: steps.config-evm-reader.outputs.impl == 'geth' uses: ./.github/actions/build-geth-evm with: - repo: ${{ steps.evm-config-reader.outputs.repo }} - ref: ${{ steps.evm-config-reader.outputs.ref }} \ No newline at end of file + repo: ${{ steps.config-evm-reader.outputs.repo }} + ref: ${{ steps.config-evm-reader.outputs.ref }} + - name: Build the EVM using EVMONE action + if: steps.config-evm-reader.outputs.impl == 'evmone' + uses: ./.github/actions/build-evmone-evm + with: + repo: ${{ steps.config-evm-reader.outputs.repo }} + ref: ${{ steps.config-evm-reader.outputs.ref }} \ No newline at end of file diff --git a/.github/actions/build-evmone-evm/action.yaml b/.github/actions/build-evmone-evm/action.yaml new file mode 100644 index 0000000000..17ed0cb398 --- /dev/null +++ b/.github/actions/build-evmone-evm/action.yaml @@ -0,0 +1,31 @@ +name: 'Build evmone EVM' +description: 'Builds the evmone EVM binary' +inputs: + repo: + description: 'Source repository to use to build the EVM binary' + required: true + default: 'ethereum/evmone' + ref: + description: 'Reference to branch, commit, or tag to use to build the EVM binary' + required: true + default: 'master' +runs: + using: "composite" + steps: + - name: Checkout evmone + uses: actions/checkout@v4 + with: + repository: ${{ inputs.repo }} + ref: ${{ inputs.ref }} + path: evmone + submodules: true + - name: Setup cmake + uses: jwlawson/actions-setup-cmake@v2 + - name: Build evmone binary + shell: bash + run: | + mkdir -p $GITHUB_WORKSPACE/bin + cd $GITHUB_WORKSPACE/evmone + cmake -S . -B build -DEVMONE_TESTING=ON + cmake --build build --parallel + echo $GITHUB_WORKSPACE/evmone/build/bin/ >> $GITHUB_PATH \ No newline at end of file diff --git a/.github/actions/build-fixtures/action.yaml b/.github/actions/build-fixtures/action.yaml new file mode 100644 index 0000000000..fcf6b78e58 --- /dev/null +++ b/.github/actions/build-fixtures/action.yaml @@ -0,0 +1,54 @@ +name: Build and Package Fixtures +inputs: + name: + description: 'Name of the fixture package' + required: true +runs: + using: "composite" + steps: + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: 3.11 + - name: Install yq + shell: bash + run: | + pip install yq + - name: Extract fixture properties + id: properties + shell: bash + run: | + yq -r --arg feature "${{ inputs.name }}" '.[$feature] | to_entries | map("\(.key)=\(.value)")[]' ./configs/feature.yaml >> "$GITHUB_OUTPUT" + - uses: ./.github/actions/build-evm + id: evm-builder + with: + type: ${{ steps.properties.outputs.evm-type }} + - name: Install solc compiler + shell: bash + run: | + if [ "$RUNNER_OS" == "Linux" ]; then PLATFORM="linux-amd64"; else PLATFORM="macosx-amd64"; fi + RELEASE_NAME=$(curl https://binaries.soliditylang.org/${PLATFORM}/list.json | jq -r --arg SOLC_VERSION "${{ steps.properties.outputs.solc }}" '.releases[$SOLC_VERSION]') + wget -O $GITHUB_WORKSPACE/bin/solc https://binaries.soliditylang.org/${PLATFORM}/$RELEASE_NAME + chmod a+x $GITHUB_WORKSPACE/bin/solc + echo $GITHUB_WORKSPACE/bin >> $GITHUB_PATH + - name: Run fixtures fill + shell: bash + run: | + pip install --upgrade pip + python -m venv env + source env/bin/activate + pip install -e . + fill -n auto --evm-bin=${{ steps.evm-builder.outputs.evm-bin }} ${{ steps.properties.outputs.fill-params }} + - name: Create fixtures info file + shell: bash + run: | + echo -e "ref: $GITHUB_REF \ncommit: $GITHUB_SHA\nbuild: $(date +"%Y-%m-%dT%H:%M:%SZ")" \ + > fixtures/info.txt + - name: Tar fixtures output + shell: bash + run: | + tar -czvf fixtures_${{ inputs.name }}.tar.gz ./fixtures + - uses: actions/upload-artifact@v4 + with: + name: fixtures_${{ inputs.name }} + path: fixtures_${{ inputs.name }}.tar.gz \ No newline at end of file diff --git a/.github/actions/build-geth-evm/action.yaml b/.github/actions/build-geth-evm/action.yaml index 02ec105c49..40831753ce 100644 --- a/.github/actions/build-geth-evm/action.yaml +++ b/.github/actions/build-geth-evm/action.yaml @@ -17,13 +17,13 @@ runs: using: "composite" steps: - name: Checkout go-ethereum - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: ${{ inputs.repo }} ref: ${{ inputs.ref }} path: go-ethereum - name: Setup golang - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: ${{ inputs.golang }} cache-dependency-path: go-ethereum/go.sum diff --git a/.github/workflows/docs_main.yaml b/.github/workflows/docs_main.yaml index c6160a6fa0..9cd97a5106 100644 --- a/.github/workflows/docs_main.yaml +++ b/.github/workflows/docs_main.yaml @@ -12,13 +12,13 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3.5.2 + uses: actions/checkout@v4 with: fetch-depth: 0 ssh-key: ${{secrets.GH_ACTIONS_DEPLOY_KEY}} - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: '3.11' diff --git a/.github/workflows/docs_tags.yaml b/.github/workflows/docs_tags.yaml index 9e12a38e12..b40bb7136d 100644 --- a/.github/workflows/docs_tags.yaml +++ b/.github/workflows/docs_tags.yaml @@ -12,13 +12,13 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3.5.2 + uses: actions/checkout@v4 with: fetch-depth: 0 ssh-key: ${{secrets.GH_ACTIONS_DEPLOY_KEY}} - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: '3.11' diff --git a/.github/workflows/fixtures.yaml b/.github/workflows/fixtures.yaml index 5312814185..37bd2fdacf 100644 --- a/.github/workflows/fixtures.yaml +++ b/.github/workflows/fixtures.yaml @@ -5,77 +5,45 @@ on: branches: - main tags: - - 'v*' + - 'v[0-9]+.[0-9]+.[0-9]+*' workflow_dispatch: jobs: + features: + runs-on: ubuntu-latest + outputs: + features: ${{ steps.parse.outputs.features }} + steps: + - uses: actions/checkout@v4 + - name: Get names from configs/feature.yaml + id: parse + shell: bash + run: | + echo "features=$(grep -Po "^[0-9a-zA-Z_\-]+" ./configs/feature.yaml | jq -R . | jq -cs .)" >> "$GITHUB_OUTPUT" build: + needs: features runs-on: ubuntu-latest strategy: matrix: - include: - - name: 'fixtures' - evm-type: 'main' - fill-params: '' - solc: '0.8.21' - python: '3.11' - # - name: 'fixtures_develop' - # evm-type: 'develop' - # fill-params: '--until=Prague' - # solc: '0.8.21' - # python: '3.11' + name: ${{ fromJson(needs.features.outputs.features) }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: true - - uses: ./.github/actions/build-evm - id: evm-builder - with: - type: ${{ matrix.evm-type }} - - name: Setup Python - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python }} - - name: Install solc compiler - shell: bash - run: | - if [ "$RUNNER_OS" == "Linux" ]; then PLATFORM="linux-amd64"; else PLATFORM="macosx-amd64"; fi - RELEASE_NAME=$(curl https://binaries.soliditylang.org/${PLATFORM}/list.json | jq -r --arg SOLC_VERSION "${{ matrix.solc }}" '.releases[$SOLC_VERSION]') - wget -O $GITHUB_WORKSPACE/bin/solc https://binaries.soliditylang.org/${PLATFORM}/$RELEASE_NAME - chmod a+x $GITHUB_WORKSPACE/bin/solc - echo $GITHUB_WORKSPACE/bin >> $GITHUB_PATH - - name: Run fixtures fill - shell: bash - run: | - pip install --upgrade pip - python -m venv env - source env/bin/activate - pip install -e . - fill ${{ matrix.fill-params }} - - name: Create fixtures info file - shell: bash - run: | - echo -e "ref: $GITHUB_REF \ncommit: $GITHUB_SHA\nbuild: $(date +"%Y-%m-%dT%H:%M:%SZ")" \ - > fixtures/info.txt - - name: Tar fixtures output - shell: bash - run: | - tar -czvf ${{ matrix.name }}.tar.gz ./fixtures - - uses: actions/upload-artifact@v3 + - uses: ./.github/actions/build-fixtures with: name: ${{ matrix.name }} - path: ${{ matrix.name }}.tar.gz release: runs-on: ubuntu-latest needs: build if: startsWith(github.ref, 'refs/tags/') steps: - name: Download artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: path: . - name: Draft Release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: files: './**' draft: true diff --git a/.github/workflows/fixtures_feature.yaml b/.github/workflows/fixtures_feature.yaml new file mode 100644 index 0000000000..3efb7e33d3 --- /dev/null +++ b/.github/workflows/fixtures_feature.yaml @@ -0,0 +1,42 @@ +name: Build and Package Fixtures for a feature + +on: + push: + branches: + - main + tags: + - '*@v*' + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Get feature name + id: feature-name + shell: bash + run: | + echo name=${GITHUB_REF_NAME//@*/} >> "$GITHUB_OUTPUT" + - uses: ./.github/actions/build-fixtures + with: + name: ${{ steps.feature-name.outputs.name }} + release: + runs-on: ubuntu-latest + needs: build + if: startsWith(github.ref, 'refs/tags/') + steps: + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + path: . + - name: Draft Pre-release + uses: softprops/action-gh-release@v2 + with: + files: './**' + draft: true + prerelease: true + generate_release_notes: true + fail_on_unmatched_files: true \ No newline at end of file diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index e7ab52f3e9..7014d1833d 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -11,25 +11,31 @@ jobs: - os: ubuntu-latest python: '3.10' solc: '0.8.20' - evm-type: 'main' + evm-type: 'stable' tox-cmd: 'tox run-parallel --parallel-no-spinner' - os: ubuntu-latest python: '3.12' solc: '0.8.23' - evm-type: 'main' + evm-type: 'stable' tox-cmd: 'tox run-parallel --parallel-no-spinner' - os: ubuntu-latest python: '3.11' solc: '0.8.21' - evm-type: 'main' # 'develop' - tox-cmd: 'tox run-parallel --parallel-no-spinner' # 'tox -e tests-develop' + evm-type: 'develop' + tox-cmd: 'tox -e tests-develop' + # Disabled to not be gated by evmone implementation + # - os: ubuntu-latest + # python: '3.11' + # solc: '0.8.21' + # evm-type: 'eip7692' + # tox-cmd: 'tox -e tests-eip7692' - os: macos-latest python: '3.11' solc: '0.8.22' - evm-type: 'main' + evm-type: 'stable' tox-cmd: 'tox run-parallel --parallel-no-spinner' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: true - uses: ./.github/actions/build-evm @@ -37,7 +43,7 @@ jobs: with: type: ${{ matrix.evm-type }} - name: Setup Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} allow-prereleases: true @@ -62,7 +68,7 @@ jobs: run: pip install tox - name: Run Tox (CPython) run: ${{ matrix.tox-cmd }} - - uses: DavidAnson/markdownlint-cli2-action@v11 + - uses: DavidAnson/markdownlint-cli2-action@v16 with: globs: | README.md diff --git a/.gitignore b/.gitignore index 4a40fef885..0c6c63da7c 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,9 @@ _readthedocs site venv-docs/ .pyspelling_en.dict + +# cached fixture downloads (consume) +cached_downloads/ +# pytest report +assets +*.html diff --git a/README.md b/README.md index 96314bc1e3..94dc4cfd22 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ The following transition tools are supported by the framework: | [ethereum/evmone](https://github.com/ethereum/evmone) | `evmone-t8n` | Yes | | [ethereum/execution-specs](https://github.com/ethereum/execution-specs) | `ethereum-spec-evm` | Yes | | [ethereum/go-ethereum](https://github.com/ethereum/go-ethereum) | [`evm t8n`](https://github.com/ethereum/go-ethereum/tree/master/cmd/evm) | Yes | -| [hyperledger/besu](https://github.com/hyperledger/besu/tree/main/ethereum/evmtool) | [`evm t8n-server`](https://github.com/hyperledger/besu/tree/main/ethereum/evmtool) | No | +| [hyperledger/besu](https://github.com/hyperledger/besu/tree/main/ethereum/evmtool) | [`evm t8n-server`](https://github.com/hyperledger/besu/tree/main/ethereum/evmtool) | Yes | | [status-im/nimbus-eth1](https://github.com/status-im/nimbus-eth1) | [`t8n`](https://github.com/status-im/nimbus-eth1/blob/master/tools/t8n/readme.md) | Yes | ### Upcoming EIP Development @@ -72,7 +72,7 @@ Generally, specific `t8n` implementations and branches must be used when develop We use named reference tags to point to the specific version of the `t8n` implementation that needs to be used fill the tests. -All current tags, their t8n implementation and branch they point to, are listed in [evm-config.yaml](evm-config.yaml). +All current tags, their t8n implementation and branch they point to, are listed in [configs/evm.yaml](configs/evm.yaml). ## Getting Started diff --git a/configs/evm.yaml b/configs/evm.yaml new file mode 100644 index 0000000000..ba25f1f776 --- /dev/null +++ b/configs/evm.yaml @@ -0,0 +1,15 @@ +stable: + impl: geth + repo: ethereum/go-ethereum + ref: master + evm-bin: evm +develop: + impl: geth + repo: lightclient/go-ethereum + ref: prague-devnet-0 + evm-bin: evm +eip7692: + impl: evmone + repo: ethereum/evmone + ref: master + evm-bin: evmone-t8n \ No newline at end of file diff --git a/configs/feature.yaml b/configs/feature.yaml new file mode 100644 index 0000000000..91861f5c0d --- /dev/null +++ b/configs/feature.yaml @@ -0,0 +1,12 @@ +stable: + evm-type: stable + fill-params: '' + solc: 0.8.21 +develop: + evm-type: develop + fill-params: --until=Prague + solc: 0.8.21 +eip7692: + evm-type: eip7692 + fill-params: --fork=CancunEIP7692 ./tests/prague + solc: 0.8.21 \ No newline at end of file diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index ab088481cd..a20bc61999 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -8,8 +8,17 @@ Test fixtures for use by clients are available for each release on the [Github r ### ๐Ÿงช Test Cases +- โœจ Add `test_create_selfdestruct_same_tx_increased_nonce` which tests self-destructing a contract with a nonce > 1 ([#478](https://github.com/ethereum/execution-spec-tests/pull/478)). - โœจ Add `test_double_kill` and `test_recreate` which test resurrection of accounts killed with `SELFDESTRUCT` ([#488](https://github.com/ethereum/execution-spec-tests/pull/488)). - โœจ Convert a few eip1153 tests from ethereum/tests repo into .py ([#440](https://github.com/ethereum/execution-spec-tests/pull/440)). +- โœจ Add eof example valid invalid tests from ori, fetch EOF Container implementation ([#535](https://github.com/ethereum/execution-spec-tests/pull/535)). +- โœจ Add tests for [EIP-2537: Precompile for BLS12-381 curve operations](https://eips.ethereum.org/EIPS/eip-2537) ([#499](https://github.com/ethereum/execution-spec-tests/pull/499)). +- โœจ [EIP-663](https://eips.ethereum.org/EIPS/eip-663): Add `test_dupn.py` and `test_swapn.py` ([#502](https://github.com/ethereum/execution-spec-tests/pull/502)). +- โœจ Add tests for [EIP-6110: Supply validator deposits on chain](https://eips.ethereum.org/EIPS/eip-6110) ([#530](https://github.com/ethereum/execution-spec-tests/pull/530)). +- โœจ Add tests for [EIP-7002: Execution layer triggerable withdrawals](https://eips.ethereum.org/EIPS/eip-7002) ([#530](https://github.com/ethereum/execution-spec-tests/pull/530)). +- โœจ Add tests for [EIP-7685: General purpose execution layer requests](https://eips.ethereum.org/EIPS/eip-7685) ([#530](https://github.com/ethereum/execution-spec-tests/pull/530)). +- โœจ Add tests for [EIP-2935: Serve historical block hashes from state +](https://eips.ethereum.org/EIPS/eip-2935) ([#564](https://github.com/ethereum/execution-spec-tests/pull/564)). ### ๐Ÿ› ๏ธ Framework @@ -19,6 +28,13 @@ Test fixtures for use by clients are available for each release on the [Github r - โœจ Libraries have been refactored to use `pydantic` for type checking in most test types ([#486](https://github.com/ethereum/execution-spec-tests/pull/486), [#501](https://github.com/ethereum/execution-spec-tests/pull/501), [#508](https://github.com/ethereum/execution-spec-tests/pull/508)). - โœจ Opcodes are now subscriptable and it's used to define the data portion of the opcode: `Op.PUSH1(1) == Op.PUSH1[1] == b"\x60\x01"` ([#513](https://github.com/ethereum/execution-spec-tests/pull/513)) - โœจ Added EOF fixture format ([#512](https://github.com/ethereum/execution-spec-tests/pull/512)). +- โœจ Verify filled EOF fixtures using `evmone-eofparse` during `fill` execution ([#519](https://github.com/ethereum/execution-spec-tests/pull/519)). +- โœจ Added `--traces` support when running with Hyperledger Besu ([#511](https://github.com/ethereum/execution-spec-tests/pull/511)). +- โœจ Use pytest's "short" traceback style (`--tb=short`) for failure summaries in the test report for more compact terminal output ([#542](https://github.com/ethereum/execution-spec-tests/pull/542)). +- โœจ The `fill` command now generates HTML test reports with links to the JSON fixtures and debug information ([#537](https://github.com/ethereum/execution-spec-tests/pull/537)). +- โœจ Add an Ethereum RPC client class for use with consume commands ([#556](https://github.com/ethereum/execution-spec-tests/pull/556)). +- โœจ Add a "slow" pytest marker, in order to be able to limit the filled tests until release ([#562](https://github.com/ethereum/execution-spec-tests/pull/562)). +- โœจ Add a CLI tool that generates blockchain tests as Python from a transaction hash ([#470](https://github.com/ethereum/execution-spec-tests/pull/470), [#576](https://github.com/ethereum/execution-spec-tests/pull/576)). ### ๐Ÿ”ง EVM Tools @@ -31,10 +47,13 @@ Test fixtures for use by clients are available for each release on the [Github r - State test field `transaction` now uses the proper zero-padded hex number format for fields `maxPriorityFeePerGas`, `maxFeePerGas`, and `maxFeePerBlobGas` - Fixtures' hashes (in the `_info` field) are now calculated by removing the "_info" field entirely instead of it being set to an empty dict. - ๐Ÿž Relax minor and patch dependency requirements to avoid conflicting package dependencies ([#510](https://github.com/ethereum/execution-spec-tests/pull/510)). +- ๐Ÿ”€ Update all CI actions to use their respective Node.js 20 versions, ahead of their Node.js 16 version deprecations ([#527](https://github.com/ethereum/execution-spec-tests/pull/527)). +- โœจ Releases now contain a `fixtures_eip7692.tar.gz` which contains all EOF fixtures ([#573](https://github.com/ethereum/execution-spec-tests/pull/573)). ### ๐Ÿ’ฅ Breaking Change - Cancun is now the latest deployed fork, and the development fork is now Prague ([#489](https://github.com/ethereum/execution-spec-tests/pull/489)). +- Stable fixtures artifact `fixtures.tar.gz` has been renamed to `fixtures_stable.tar.gz` ([#573](https://github.com/ethereum/execution-spec-tests/pull/573)) ## ๐Ÿ”œ [v2.1.1](https://github.com/ethereum/execution-spec-tests/releases/tag/v2.1.1) - 2024-03-09 @@ -46,6 +65,11 @@ Test fixtures for use by clients are available for each release on the [Github r ### ๐Ÿ› ๏ธ Framework +- โœจ Adds two `consume` commands [#339](https://github.com/ethereum/execution-spec-tests/pull/339): + + 1. `consume direct` - Execute a test fixture directly against a client using a `blocktest`-like command (currently only geth supported). + 2. `consume rlp` - Execute a test fixture in a hive simulator against a client that imports the test's genesis config and blocks as RLP upon startup. This is a re-write of the [ethereum/consensus](https://github.com/ethereum/hive/tree/master/simulators/ethereum/consensus) Golang simulator. + - โœจ Add Prague to forks ([#419](https://github.com/ethereum/execution-spec-tests/pull/419)). - โœจ Improve handling of the argument passed to `solc --evm-version` when compiling Yul code ([#418](https://github.com/ethereum/execution-spec-tests/pull/418)). - ๐Ÿž Fix `fill -m yul_test` which failed to filter tests that are (dynamically) marked as a yul test ([#418](https://github.com/ethereum/execution-spec-tests/pull/418)). diff --git a/docs/gen_test_case_reference.py b/docs/gen_test_case_reference.py index d2f4102e09..a624baa793 100644 --- a/docs/gen_test_case_reference.py +++ b/docs/gen_test_case_reference.py @@ -186,21 +186,24 @@ def run_collect_only(test_path: Path = source_directory) -> Tuple[str, str]: str: The command used to collect the tests. str: A list of the collected tests. """ - buffer = io.StringIO() - with contextlib.redirect_stdout(buffer): - pytest.main(["--collect-only", "-q", "--until", DEV_FORKS[-1], str(test_path)]) - output = buffer.getvalue() - collect_only_command = f"fill --collect-only -q --until {DEV_FORKS[-1]} {test_path}" - # strip out the test module - output_lines = [ - line.split("::")[1] - for line in output.split("\n") - if line.startswith("tests/") and "::" in line - ] - # prefix with required indent for admonition in MARKDOWN_TEST_CASES_TEMPLATE - collect_only_output = "\n".join(" " + line for line in output_lines) - collect_only_output = collect_only_output[4:] # strip out indent for first line - return collect_only_command, collect_only_output + for fork in DEV_FORKS: + collect_only_args = ["--collect-only", "-q", "--until", fork, str(test_path)] + buffer = io.StringIO() + with contextlib.redirect_stdout(buffer): + pytest.main(collect_only_args) + output = buffer.getvalue() + # strip out the test module + output_lines = [ + line.split("::")[1] + for line in output.split("\n") + if line.startswith("tests/") and "::" in line + ] + # prefix with required indent for admonition in MARKDOWN_TEST_CASES_TEMPLATE + collect_only_output = "\n".join(" " + line for line in output_lines) + collect_only_output = collect_only_output[4:] # strip out indent for first line + if collect_only_output: + break + return f'fill {" ".join(collect_only_args)}', collect_only_output def generate_github_url(file_path, branch_or_commit_or_tag="main"): diff --git a/docs/getting_started/debugging_t8n_tools.md b/docs/getting_started/debugging_t8n_tools.md index 718c54c820..5f8ad85591 100644 --- a/docs/getting_started/debugging_t8n_tools.md +++ b/docs/getting_started/debugging_t8n_tools.md @@ -3,11 +3,18 @@ There are two flags that can help debugging `t8n` tools or the execution-spec-tests framework: 1. `--evm-dump-dir`: Write debug information from `t8n` tool calls to the specified directory. -2. `--verify-fixtures`: Run go-ethereum's `evm blocktest` command to verify the generated test fixtures. +2. `--traces`: Collect traces of the execution from the transition tool. +3. `--verify-fixtures`: Run go-ethereum's `evm blocktest` command to verify the generated test fixtures. ## EVM Dump Directory -The `--evm-dump-dir` flag tells the framework to write the inputs and outputs of every call made to the `t8n` command to help debugging or simply understand how a test is interacting with the EVM. +The `--evm-dump-dir` flag tells the framework to write the inputs and outputs of every call made to the `t8n` command to the specified output directory. The aim is to help debugging or simply understand how a test is interacting with the EVM. + +Each test case receives its own sub-directory under the `--evm-dump-dir` that contains these files which can be easily accessed from the HTML test report generated by `fill` (located by default in the root of the `--output` directory). + +
+ ![HTML Report Summary](./img/evm_dump_dir_in_html_report.png){width=auto align=center} +
In particular, a script `t8n.sh` is generated for each call to the `t8n` command which can be used to reproduce the call to trigger errors or attach a debugger without the need to execute Python. @@ -15,7 +22,7 @@ For example, running: ```console fill tests/berlin/eip2930_access_list/ --fork Berlin -m blockchain_test \ - --evm-dump-dir=/tmp/evm-dump + --evm-dump-dir=/tmp/evm-dump --traces ``` will produce the directory structure: @@ -38,7 +45,8 @@ will produce the directory structure: ย ย  โ”œโ”€โ”€ ๐Ÿ“„ stderr.txt ย ย  โ”œโ”€โ”€ ๐Ÿ“„ stdin.txt ย ย  โ”œโ”€โ”€ ๐Ÿ“„ stdout.txt - ย ย  โ””โ”€โ”€ ๐Ÿ“„ t8n.sh + ย ย  โ”œโ”€โ”€ ๐Ÿ“„ t8n.sh + โ””โ”€โ”€ ๐Ÿ“„ trace-0-0x5c4f07ce52f0a276a06aabdfff16cc693b5e007c018f9a42431e68200e2da515.jsonl ``` where the directory `0` is the starting index of the different calls made to the `t8n` tool executed during the test, and since the test only contains one block, there is only one directory present. diff --git a/docs/getting_started/executing_tests_command_line.md b/docs/getting_started/executing_tests_command_line.md index d678dc6adc..119c03dad3 100644 --- a/docs/getting_started/executing_tests_command_line.md +++ b/docs/getting_started/executing_tests_command_line.md @@ -154,6 +154,10 @@ Arguments defining filler location and output: Don't group fixtures in JSON files by test function; write each fixture to its own file. This can be used to increase the granularity of --verify-fixtures. + --no-html Don't generate an HTML test report (in the output + directory). The --html flag can be used to specify a + different path. + Arguments defining debug behavior: --evm-dump-dir EVM_DUMP_DIR, --t8n-dump-dir EVM_DUMP_DIR @@ -170,5 +174,4 @@ Arguments related to running execution-spec-tests: and exit. Exit: After displaying help. - ``` diff --git a/docs/getting_started/img/evm_dump_dir_in_html_report.png b/docs/getting_started/img/evm_dump_dir_in_html_report.png new file mode 100644 index 0000000000..1979b5dd40 Binary files /dev/null and b/docs/getting_started/img/evm_dump_dir_in_html_report.png differ diff --git a/docs/getting_started/img/pytest_collect_only.png b/docs/getting_started/img/pytest_collect_only.png index cc2a075f18..b850fbc1fd 100644 Binary files a/docs/getting_started/img/pytest_collect_only.png and b/docs/getting_started/img/pytest_collect_only.png differ diff --git a/docs/getting_started/img/pytest_run_example.png b/docs/getting_started/img/pytest_run_example.png index cc23b17675..9ee23e3dc7 100644 Binary files a/docs/getting_started/img/pytest_run_example.png and b/docs/getting_started/img/pytest_run_example.png differ diff --git a/docs/getting_started/quick_start.md b/docs/getting_started/quick_start.md index 037908c312..f1166eb643 100644 --- a/docs/getting_started/quick_start.md +++ b/docs/getting_started/quick_start.md @@ -81,7 +81,8 @@ The following requires a Python 3.10, 3.11 or 3.12 installation. Check: 1. The versions of the `evm` and `solc` tools are as expected (your versions may differ from those in the highlighted box). - 2. The corresponding fixture file has been generated: + 2. The generated HTML test report by clicking the link at the bottom of the console output. + 3. The corresponding fixture file has been generated: ```console head fixtures/blockchain_tests/berlin/eip2930_access_list/acl/access_list.json diff --git a/docs/index.md b/docs/index.md index f92361d3f7..f98bd1d54c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -67,7 +67,7 @@ The following transition tools are supported by the framework: | [ethereum/evmone](https://github.com/ethereum/evmone) | `evmone-t8n` | Yes | | [ethereum/execution-specs](https://github.com/ethereum/execution-specs) | `ethereum-spec-evm` | Yes | | [ethereum/go-ethereum](https://github.com/ethereum/go-ethereum) | [`evm t8n`](https://github.com/ethereum/go-ethereum/tree/master/cmd/evm) | Yes | -| [hyperledger/besu](https://github.com/hyperledger/besu/tree/main/ethereum/evmtool) | [`evm t8n-server`](https://github.com/hyperledger/besu/tree/main/ethereum/evmtool) | No | +| [hyperledger/besu](https://github.com/hyperledger/besu/tree/main/ethereum/evmtool) | [`evmtool t8n-server`](https://github.com/hyperledger/besu/tree/main/ethereum/evmtool) | Yes | | [status-im/nimbus-eth1](https://github.com/status-im/nimbus-eth1) | [`t8n`](https://github.com/status-im/nimbus-eth1/blob/master/tools/t8n/readme.md) | Yes | ## Relationship to ethereum/tests diff --git a/evm-config.yaml b/evm-config.yaml deleted file mode 100644 index 5aef7c9e77..0000000000 --- a/evm-config.yaml +++ /dev/null @@ -1,8 +0,0 @@ -main: - impl: geth - repo: ethereum/go-ethereum - ref: master -develop: - impl: geth - repo: ethereum/go-ethereum - ref: master \ No newline at end of file diff --git a/pytest.ini b/pytest.ini index 42e18627b9..078ab40add 100644 --- a/pytest.ini +++ b/pytest.ini @@ -3,11 +3,14 @@ console_output_style = count minversion = 7.0 python_files = *.py testpaths = tests/ +markers = + slow addopts = -p pytest_plugins.test_filler.test_filler -p pytest_plugins.forks.forks -p pytest_plugins.spec_version_checker.spec_version_checker -p pytest_plugins.test_help.test_help -m "not eip_version_check" + --tb short --dist loadscope --ignore tests/cancun/eip4844_blobs/point_evaluation_vectors/ diff --git a/setup.cfg b/setup.cfg index 87e7fa2879..659c91453e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -23,12 +23,19 @@ package_dir = python_requires = >=3.10 install_requires = - ethereum@git+https://github.com/ethereum/execution-specs.git + click>=8.1.0,<9 + ethereum@git+https://github.com/ethereum/execution-specs + hive.py@git+https://github.com/danceratopz/hive.py@chore/setup.cfg/move-mypy-deps-to-lint-extras setuptools types-setuptools + PyJWT>=2.3.0,<3 + tenacity>8.2.0,<9 + bidict>=0.23,<1 requests>=2.31.0,<3 colorlog>=6.7.0,<7 pytest>7.3.2,<8 + pytest-html>=4.1.0,<5 + pytest-metadata>=3,<4 pytest-xdist>=3.3.1,<4 coincurve>=18.0.0,<19 trie>=2.0.2,<3 @@ -42,14 +49,19 @@ ethereum_test_tools = py.typed ethereum_test_forks = py.typed + forks/*.bin evm_transition_tool = py.typed +pytest_plugins = + py.typed [options.entry_points] console_scripts = fill = cli.pytest_commands:fill tf = cli.pytest_commands:tf checkfixtures = cli.check_fixtures:check_fixtures + consume = cli.pytest_commands:consume + genindex = cli.gen_index:generate_fixtures_index_cli gentest = cli.gentest:make_test pyspelling_soft_fail = cli.tox_helpers:pyspelling markdownlintcli2_soft_fail = cli.tox_helpers:markdownlint diff --git a/src/cli/gen_index.py b/src/cli/gen_index.py new file mode 100644 index 0000000000..639befe0ab --- /dev/null +++ b/src/cli/gen_index.py @@ -0,0 +1,218 @@ +""" +Generate an index file of all the json fixtures in the specified directory. +""" +import datetime +import json +import os +from pathlib import Path +from typing import List + +import click +import rich +from rich.progress import ( + BarColumn, + Column, + Progress, + TaskProgressColumn, + TextColumn, + TimeElapsedColumn, +) + +from ethereum_test_tools.common.base_types import HexNumber +from ethereum_test_tools.spec.consume.types import IndexFile, TestCaseIndexFile +from ethereum_test_tools.spec.file.types import Fixtures +from evm_transition_tool import FixtureFormats + +from .hasher import HashableItem + + +def count_json_files_exclude_index(start_path: Path) -> int: + """ + Return the number of json files in the specified directory, excluding + index.json files and tests in "blockchain_tests_hive". + """ + json_file_count = sum( + 1 + for file in start_path.rglob("*.json") + if file.name != "index.json" and "blockchain_tests_hive" not in file.parts + ) + return json_file_count + + +def infer_fixture_format_from_path(file: Path) -> FixtureFormats: + """ + Attempt to infer the fixture format from the file path. + """ + if "blockchain_tests_hive" in file.parts: + return FixtureFormats.BLOCKCHAIN_TEST_HIVE + if "blockchain_tests" in file.parts: + return FixtureFormats.BLOCKCHAIN_TEST + if "state_tests" in file.parts: + return FixtureFormats.STATE_TEST + return FixtureFormats.UNSET_TEST_FORMAT + + +@click.command( + help=( + "Generate an index file of all the json fixtures in the specified directory." + "The index file is saved as 'index.json' in the specified directory." + ) +) +@click.option( + "--input", + "-i", + "input_dir", + type=click.Path(exists=True, file_okay=False, dir_okay=True, readable=True), + required=True, + help="The input directory", +) +@click.option( + "--disable-infer-format", + "-d", + "disable_infer_format", + is_flag=True, + default=False, + expose_value=True, + help="Don't try to guess the fixture format from the json file's path.", +) +@click.option( + "--quiet", + "-q", + "quiet_mode", + is_flag=True, + default=False, + expose_value=True, + help="Don't show the progress bar while processing fixture files.", +) +@click.option( + "--force", + "-f", + "force_flag", + is_flag=True, + default=False, + expose_value=True, + help="Force re-generation of the index file, even if it already exists.", +) +def generate_fixtures_index_cli( + input_dir: str, quiet_mode: bool, force_flag: bool, disable_infer_format: bool +): + """ + The CLI wrapper to an index of all the fixtures in the specified directory. + """ + generate_fixtures_index( + Path(input_dir), + quiet_mode=quiet_mode, + force_flag=force_flag, + disable_infer_format=disable_infer_format, + ) + + +def generate_fixtures_index( + input_path: Path, + quiet_mode: bool = False, + force_flag: bool = False, + disable_infer_format: bool = False, +): + """ + Generate an index file (index.json) of all the fixtures in the specified + directory. + """ + total_files = 0 + if not os.path.isdir(input_path): # caught by click if using via cli + raise FileNotFoundError(f"The directory {input_path} does not exist.") + if not quiet_mode: + total_files = count_json_files_exclude_index(input_path) + + output_file = Path(f"{input_path}/index.json") + try: + root_hash = HashableItem.from_folder(folder_path=input_path).hash() + except (KeyError, TypeError): + root_hash = b"" # just regenerate a new index file + + if not force_flag and output_file.exists(): + index_data: IndexFile + try: + with open(output_file, "r") as f: + index_data = IndexFile(**json.load(f)) + if index_data.root_hash and index_data.root_hash == HexNumber(root_hash): + if not quiet_mode: + rich.print(f"Index file [bold cyan]{output_file}[/] is up-to-date.") + return + except Exception as e: + rich.print(f"Ignoring exception {e}") + rich.print(f"...generating a new index file [bold cyan]{output_file}[/]") + + filename_display_width = 25 + with Progress( + TextColumn( + f"[bold cyan]{{task.fields[filename]:<{filename_display_width}}}[/]", + justify="left", + table_column=Column(ratio=1), + ), + BarColumn( + complete_style="green3", + finished_style="bold green3", + table_column=Column(ratio=2), + ), + TaskProgressColumn(), + TimeElapsedColumn(), + expand=False, + disable=quiet_mode, + ) as progress: + task_id = progress.add_task("[cyan]Processing files...", total=total_files, filename="...") + + test_cases: List[TestCaseIndexFile] = [] + for file in input_path.rglob("*.json"): + if file.name == "index.json": + continue + if "blockchain_tests_hive" in file.parts: + continue + + try: + fixture_format = None + if not disable_infer_format: + fixture_format = infer_fixture_format_from_path(file) + fixtures = Fixtures.from_file(file, fixture_format=fixture_format) + except Exception as e: + rich.print(f"[red]Error loading fixtures from {file}[/red]") + raise e + + relative_file_path = Path(file).absolute().relative_to(Path(input_path).absolute()) + for fixture_name, fixture in fixtures.items(): + test_cases.append( + TestCaseIndexFile( + id=fixture_name, + json_path=relative_file_path, + fixture_hash=fixture.info.get("hash", None), + fork=fixture.get_fork(), + format=fixture.format, + ) + ) + + display_filename = file.name + if len(display_filename) > filename_display_width: + display_filename = display_filename[: filename_display_width - 3] + "..." + else: + display_filename = display_filename.ljust(filename_display_width) + + progress.update(task_id, advance=1, filename=display_filename) + + progress.update( + task_id, + completed=total_files, + filename="Indexing complete ๐Ÿฆ„".ljust(filename_display_width), + ) + + index = IndexFile( + test_cases=test_cases, + root_hash=root_hash, + created_at=datetime.datetime.now(), + test_count=len(test_cases), + ) + + with open(output_file, "w") as f: + f.write(index.model_dump_json(exclude_none=False, indent=2)) + + +if __name__ == "__main__": + generate_fixtures_index_cli() diff --git a/src/cli/gentest.py b/src/cli/gentest.py index 1eb0715961..2a7ba97877 100644 --- a/src/cli/gentest.py +++ b/src/cli/gentest.py @@ -1,5 +1,54 @@ """ -Define an entry point wrapper for test generator. +Generate a Python blockchain test from a transaction hash. + +This script can be used to generate Python source for a blockchain test case +that replays a mainnet or testnet transaction from its transaction hash. + +Note: + +Requirements: + +1. Access to an archive node for the network where the transaction + originates. A provider may be used. +2. A config file with the remote node data in JSON format + + ```json + { + "remote_nodes" : [ + { + "name" : "mainnet_archive", + "node_url" : "https://example.archive.node.url/v1 + "client_id" : "", + "secret" : "" + } + ] + } + ``` + + `client_id` and `secret` may be left empty if the node does not require + them. +3. The transaction hash of a type 0 transaction (currently only legacy + transactions are supported). + +Example Usage: + +1. Generate a test for a transaction with hash + + ```console + gentest -c config.json \ + 0xa41f343be7a150b740e5c939fa4d89f3a2850dbe21715df96b612fc20d1906be \ + tests/paris/test_0xa41f.py + ``` + +2. Fill the test: + + ```console + fill --fork=Paris tests/paris/test_0xa41f.py + ``` + +Limitations: + +1. Only legacy transaction types (type 0) are currently supported. """ import json @@ -115,7 +164,7 @@ def _make_pre_state( state_str += pad + "storage={\n" if account_obj.storage is not None: - for record, value in account_obj.storage.items(): + for record, value in account_obj.storage.root.items(): pad_record = common.ZeroPaddedHexNumber(record) pad_value = common.ZeroPaddedHexNumber(value) state_str += f'{pad} "{pad_record}" : "{pad_value}",\n' @@ -141,7 +190,7 @@ def _make_transaction(self, test: str, tr: "RequestManager.RemoteTransaction") - "gas_limit", "value", ] - for field, value in asdict(tr.transaction).items(): + for field, value in iter(tr.transaction): if value is None: continue @@ -244,6 +293,7 @@ def _make_request(self, data) -> requests.Response: error_str = "An error occurred while making remote request: " try: response = requests.post(self.node_url, headers=self.headers, data=json.dumps(data)) + response.raise_for_status() if response.status_code >= 200 and response.status_code < 300: return response else: @@ -267,6 +317,10 @@ def eth_get_transaction_by_hash(self, transaction_hash: str) -> RemoteTransactio response = self._make_request(data) res = response.json().get("result", None) + assert ( + res["type"] == "0x0" + ), f"Transaction has type {res['type']}: Currently only type 0 transactions are supported." + return RequestManager.RemoteTransaction( block_number=res["blockNumber"], tr_hash=res["hash"], diff --git a/src/cli/hasher.py b/src/cli/hasher.py index 6073899ad2..e724d57fc5 100644 --- a/src/cli/hasher.py +++ b/src/cli/hasher.py @@ -75,12 +75,16 @@ def from_json_file(cls, *, file_path: Path, parents: List[str]) -> "HashableItem with file_path.open("r") as f: data = json.load(f) for key, item in sorted(data.items()): - assert isinstance(item, dict), f"Expected dict, got {type(item)}" - assert "_info" in item, f"Expected _info in {key}" - assert "hash" in item["_info"], f"Expected hash in {key}" - assert isinstance( - item["_info"]["hash"], str - ), f"Expected hash to be a string in {key}, got {type(item['_info']['hash'])}" + if not isinstance(item, dict): + raise TypeError(f"Expected dict, got {type(item)} for {key}") + if "_info" not in item: + raise KeyError(f"Expected '_info' in {key}") + if "hash" not in item["_info"]: + raise KeyError(f"Expected 'hash' in {key}") + if not isinstance(item["_info"]["hash"], str): + raise TypeError( + f"Expected hash to be a string in {key}, got {type(item['_info']['hash'])}" + ) item_hash_bytes = bytes.fromhex(item["_info"]["hash"][2:]) items[key] = cls( type=HashableItemType.TEST, @@ -96,6 +100,8 @@ def from_folder(cls, *, folder_path: Path, parents: List[str] = []) -> "Hashable """ items = {} for file_path in sorted(folder_path.iterdir()): + if file_path.name == "index.json": + continue if file_path.is_file() and file_path.suffix == ".json": item = cls.from_json_file( file_path=file_path, parents=parents + [folder_path.name] diff --git a/src/cli/pytest_commands.py b/src/cli/pytest_commands.py index bdb8ef20b8..34bb558d30 100644 --- a/src/cli/pytest_commands.py +++ b/src/cli/pytest_commands.py @@ -34,8 +34,10 @@ ``` """ +import os import sys -from typing import Any, Callable, List +import warnings +from typing import Any, Callable, List, Literal import click import pytest @@ -91,7 +93,7 @@ def handle_help_flags( pytest_args: List[str], help_flag: bool, pytest_help_flag: bool ) -> List[str]: """ - Modify the arguments passed to the click CLI command before forwarding to + Modifies the help arguments passed to the click CLI command before forwarding to the pytest command. This is to make `--help` more useful because `pytest --help` is extremely @@ -105,12 +107,139 @@ def handle_help_flags( return list(pytest_args) +def handle_stdout_flags(args): + """ + If the user has requested to write to stdout, add pytest arguments in order + to suppress pytest's test session header and summary output. + """ + writing_to_stdout = False + if any(arg == "--output=stdout" for arg in args): + writing_to_stdout = True + elif "--output" in args: + output_index = args.index("--output") + if args[output_index + 1] == "stdout": + writing_to_stdout = True + if writing_to_stdout: + if any(arg == "-n" or arg.startswith("-n=") for arg in args): + sys.exit("error: xdist-plugin not supported with --output=stdout (remove -n args).") + args.extend(["-qq", "-s", "--no-html"]) + return args + + @click.command(context_settings=dict(ignore_unknown_options=True)) @common_click_options -def fill(pytest_args: List[str], help_flag: bool, pytest_help_flag: bool) -> None: +def fill( + pytest_args: List[str], + help_flag: bool, + pytest_help_flag: bool, +) -> None: """ Entry point for the fill command. """ args = handle_help_flags(pytest_args, help_flag, pytest_help_flag) + args = handle_stdout_flags(args) result = pytest.main(args) sys.exit(result) + + +def get_hive_flags_from_env(): + """ + Read simulator flags from environment variables and convert them, as best as + possible, into pytest flags. + """ + pytest_args = [] + xdist_workers = os.getenv("HIVE_PARALLELISM") + if xdist_workers is not None: + pytest_args.extend(["-n", xdist_workers]) + test_pattern = os.getenv("HIVE_TEST_PATTERN") + if test_pattern is not None: + # TODO: Check that the regex is a valid pytest -k "test expression" + pytest_args.extend(["-k", test_pattern]) + random_seed = os.getenv("HIVE_RANDOM_SEED") + if random_seed is not None: + # TODO: implement random seed + warnings.warn("HIVE_RANDOM_SEED is not yet supported.") + log_level = os.getenv("HIVE_LOGLEVEL") + if log_level is not None: + # TODO add logging within simulators and implement log level via cli + warnings.warn("HIVE_LOG_LEVEL is not yet supported.") + return pytest_args + + +ConsumeCommands = Literal["dirct", "rlp", "engine", "all"] + + +def consume_ini_path(consume_command: ConsumeCommands) -> str: + """ + Get the path to the ini file for the specified consume command. + """ + return f"src/pytest_plugins/consume/ini_files/pytest-consume-{consume_command}.ini" + + +@click.group() +def consume(): + """ + Help clients consume JSON test fixtures. + """ + pass + + +@click.command(context_settings=dict(ignore_unknown_options=True)) +@common_click_options +def consume_direct(pytest_args, help_flag, pytest_help_flag): + """ + Clients consume directly via the `blocktest` interface. + """ + args = handle_help_flags(pytest_args, help_flag, pytest_help_flag) + args += ["-c", consume_ini_path("direct"), "--rootdir", "./"] + if not sys.stdin.isatty(): # the command is receiving input on stdin + args.extend(["-s", "--input=stdin"]) + pytest.main(args) + + +@click.command(context_settings=dict(ignore_unknown_options=True)) +@common_click_options +def consume_via_rlp(pytest_args, help_flag, pytest_help_flag): + """ + Clients consume RLP-encoded blocks on startup. + """ + args = handle_help_flags(pytest_args, help_flag, pytest_help_flag) + args += ["-c", consume_ini_path("rlp"), "--rootdir", "./"] + args += get_hive_flags_from_env() + if not sys.stdin.isatty(): # the command is receiving input on stdin + args.extend(["-s", "--input=stdin"]) + pytest.main(args) + + +@click.command(context_settings=dict(ignore_unknown_options=True)) +@common_click_options +def consume_via_engine_api(pytest_args, help_flag, pytest_help_flag): + """ + Clients consume via the Engine API. + """ + args = handle_help_flags(pytest_args, help_flag, pytest_help_flag) + args += ["-c", consume_ini_path("engine"), "--rootdir", "./"] + args += get_hive_flags_from_env() + if not sys.stdin.isatty(): # the command is receiving input on stdin + args.extend(["-s", "--input=stdin"]) + pytest.main(args) + + +@click.command(context_settings=dict(ignore_unknown_options=True)) +@common_click_options +def consume_all(pytest_args, help_flag, pytest_help_flag): + """ + Clients consume via all available methods (direct, rlp, engine). + """ + args = handle_help_flags(pytest_args, help_flag, pytest_help_flag) + args += ["-c", consume_ini_path("all"), "--rootdir", "./"] + args += get_hive_flags_from_env() + if not sys.stdin.isatty(): # the command is receiving input on stdin + args.extend(["-s", "--input=stdin"]) + pytest.main(args) + + +consume.add_command(consume_all, name="all") +consume.add_command(consume_direct, name="direct") +consume.add_command(consume_via_rlp, name="rlp") +consume.add_command(consume_via_engine_api, name="engine") diff --git a/src/cli/tests/test_pytest_commands.py b/src/cli/tests/test_pytest_commands.py index 227b1db541..219c5f0058 100644 --- a/src/cli/tests/test_pytest_commands.py +++ b/src/cli/tests/test_pytest_commands.py @@ -2,9 +2,15 @@ Tests for pytest commands (e.g., fill) click CLI. """ +from pathlib import Path +from tempfile import TemporaryDirectory +from typing import Generator + import pytest from click.testing import CliRunner +import pytest_plugins.test_filler.test_filler + from ..pytest_commands import fill @@ -53,3 +59,132 @@ def test_tf_deprecation(runner): result = runner.invoke(tf, []) assert result.exit_code == 1 assert "The `tf` command-line tool has been superseded by `fill`" in result.output + + +class TestHtmlReportFlags: + """ + Test html report generation and output options. + """ + + @pytest.fixture + def fill_args(self): + """ + Provides default arguments for the `fill` command when testing html report + generation. + + Specifies a single existing example test case for faster fill execution, + and to allow for tests to check for the fixture generation location. + """ + return ["-k", "test_dup and state_test-DUP16", "--fork", "Frontier"] + + @pytest.fixture() + def default_html_report_filename(self): + """ + The default filename for fill's pytest html report. + """ + return pytest_plugins.test_filler.test_filler.default_html_report_filename() + + @pytest.fixture(scope="function") + def temp_dir(self) -> Generator[Path, None, None]: # noqa: D102 + temp_dir = TemporaryDirectory() + yield Path(temp_dir.name) + temp_dir.cleanup() + + @pytest.fixture(scope="function", autouse=True) + def monkeypatch_default_output_directory(self, monkeypatch, temp_dir): + """ + Monkeypatch default output directory for the pytest commands. + + This avoids using the local directory in user space for the output of pytest + commands and uses the a temporary directory instead. + """ + + def mock_default_output_directory(): + return temp_dir + + monkeypatch.setattr( + pytest_plugins.test_filler.test_filler, + "default_output_directory", + mock_default_output_directory, + ) + + def test_fill_default_output_options( + self, + runner, + temp_dir, + fill_args, + default_html_report_filename, + ): + """ + Test default pytest html behavior: Neither `--html` or `--output` is specified. + """ + default_html_path = temp_dir / default_html_report_filename + result = runner.invoke(fill, fill_args) + assert result.exit_code == pytest.ExitCode.OK + assert default_html_path.exists() + + def test_fill_no_html_option( + self, + runner, + temp_dir, + fill_args, + default_html_report_filename, + ): + """ + Test pytest html report is disabled with the `--no-html` flag. + """ + default_html_path = temp_dir / default_html_report_filename + fill_args += ["--no-html"] + result = runner.invoke(fill, fill_args) + assert result.exit_code == pytest.ExitCode.OK + assert not default_html_path.exists() + + def test_fill_html_option( + self, + runner, + temp_dir, + fill_args, + ): + """ + Tests pytest html report generation with only the `--html` flag. + """ + non_default_html_path = temp_dir / "non_default_output_dir" / "report.html" + fill_args += ["--html", str(non_default_html_path)] + result = runner.invoke(fill, fill_args) + assert result.exit_code == pytest.ExitCode.OK + assert non_default_html_path.exists() + + def test_fill_output_option( + self, + runner, + temp_dir, + fill_args, + default_html_report_filename, + ): + """ + Tests pytest html report generation with only the `--output` flag. + """ + output_dir = temp_dir / "non_default_output_dir" + non_default_html_path = output_dir / default_html_report_filename + fill_args += ["--output", str(output_dir)] + result = runner.invoke(fill, fill_args) + assert result.exit_code == pytest.ExitCode.OK + assert non_default_html_path.exists() + assert (output_dir / "state_tests").exists(), "No fixtures in output directory" + + def test_fill_html_and_output_options( + self, + runner, + temp_dir, + fill_args, + ): + """ + Tests pytest html report generation with both `--output` and `--html` flags. + """ + output_dir = temp_dir / "non_default_output_dir_fixtures" + html_path = temp_dir / "non_default_output_dir_html" / "non_default.html" + fill_args += ["--output", str(output_dir), "--html", str(html_path)] + result = runner.invoke(fill, fill_args) + assert result.exit_code == pytest.ExitCode.OK + assert html_path.exists() + assert (output_dir / "state_tests").exists(), "No fixtures in output directory" diff --git a/src/ethereum_test_forks/__init__.py b/src/ethereum_test_forks/__init__.py index 29c76a0496..456679967b 100644 --- a/src/ethereum_test_forks/__init__.py +++ b/src/ethereum_test_forks/__init__.py @@ -17,6 +17,7 @@ London, MuirGlacier, Paris, + Prague, Shanghai, ) from .forks.transition import ( @@ -60,6 +61,7 @@ "Shanghai", "ShanghaiToCancunAtTime15k", "Cancun", + "Prague", "get_transition_forks", "forks_from", "forks_from_until", diff --git a/src/ethereum_test_forks/base_fork.py b/src/ethereum_test_forks/base_fork.py index c8823fed33..4126498e28 100644 --- a/src/ethereum_test_forks/base_fork.py +++ b/src/ethereum_test_forks/base_fork.py @@ -149,6 +149,14 @@ def header_beacon_root_required(cls, block_number: int, timestamp: int) -> bool: """ pass + @classmethod + @abstractmethod + def header_requests_required(cls, block_number: int, timestamp: int) -> bool: + """ + Returns true if the header must contain beacon chain requests + """ + pass + @classmethod @abstractmethod def blob_gas_per_blob(cls, block_number: int, timestamp: int) -> int: diff --git a/src/ethereum_test_forks/forks/deposit_contract.bin b/src/ethereum_test_forks/forks/deposit_contract.bin new file mode 100644 index 0000000000..4e44873473 Binary files /dev/null and b/src/ethereum_test_forks/forks/deposit_contract.bin differ diff --git a/src/ethereum_test_forks/forks/forks.py b/src/ethereum_test_forks/forks/forks.py index 1fba542a3e..4338e6e9d0 100644 --- a/src/ethereum_test_forks/forks/forks.py +++ b/src/ethereum_test_forks/forks/forks.py @@ -2,12 +2,18 @@ All Ethereum fork class definitions. """ +from hashlib import sha256 +from os.path import realpath +from pathlib import Path from typing import List, Mapping, Optional from semver import Version from ..base_fork import BaseFork +CURRENT_FILE = Path(realpath(__file__)) +CURRENT_FOLDER = CURRENT_FILE.parent + # All forks must be listed here !!! in the order they were introduced !!! class Frontier(BaseFork, solc_name="homestead"): @@ -89,6 +95,13 @@ def blob_gas_per_blob(cls, block_number: int, timestamp: int) -> int: """ return 0 + @classmethod + def header_requests_required(cls, block_number: int, timestamp: int) -> bool: + """ + At genesis, header must not contain beacon chain requests. + """ + return False + @classmethod def engine_new_payload_version( cls, block_number: int = 0, timestamp: int = 0 @@ -474,3 +487,123 @@ def solc_min_version(cls) -> Version: Returns the minimum version of solc that supports this fork. """ return Version.parse("1.0.0") # set a high version; currently unknown + + @classmethod + def precompiles(cls, block_number: int = 0, timestamp: int = 0) -> List[int]: + """ + At Prague, pre-compile for BLS operations are added: + + G1ADD = 0x0B + G1MUL = 0x0C + G1MSM = 0x0D + G2ADD = 0x0E + G2MUL = 0x0F + G2MSM = 0x10 + PAIRING = 0x11 + MAP_FP_TO_G1 = 0x12 + MAP_FP2_TO_G2 = 0x13 + """ + return list(range(0xB, 0x13 + 1)) + super(Prague, cls).precompiles(block_number, timestamp) + + @classmethod + def pre_allocation_blockchain(cls) -> Mapping: + """ + Prague requires pre-allocation of the beacon chain deposit contract for EIP-6110, + the exits contract for EIP-7002, and the history storage contract for EIP-2935. + """ + new_allocation = {} + + # Add the beacon chain deposit contract + DEPOSIT_CONTRACT_TREE_DEPTH = 32 + storage = {} + next_hash = sha256(b"\x00" * 64).digest() + for i in range(DEPOSIT_CONTRACT_TREE_DEPTH + 2, DEPOSIT_CONTRACT_TREE_DEPTH * 2 + 1): + storage[i] = next_hash + next_hash = sha256(next_hash + next_hash).digest() + + with open(CURRENT_FOLDER / "deposit_contract.bin", mode="rb") as f: + new_allocation.update( + { + 0x00000000219AB540356CBB839CBE05303D7705FA: { + "nonce": 1, + "code": f.read(), + "storage": storage, + } + } + ) + + # Add the withdrawal request contract + with open(CURRENT_FOLDER / "withdrawal_request.bin", mode="rb") as f: + new_allocation.update( + { + 0x00A3CA265EBCB825B45F985A16CEFB49958CE017: { + "nonce": 1, + "code": f.read(), + }, + } + ) + + # Add the history storage contract + with open(CURRENT_FOLDER / "history_contract.bin", mode="rb") as f: + new_allocation.update( + { + 0x25A219378DAD9B3503C8268C9CA836A52427A4FB: { + "nonce": 1, + "code": f.read(), + } + } + ) + + return new_allocation | super(Prague, cls).pre_allocation_blockchain() + + @classmethod + def header_requests_required(cls, block_number: int, timestamp: int) -> bool: + """ + Prague requires that the execution layer block contains the beacon + chain requests. + """ + return True + + @classmethod + def engine_new_payload_version( + cls, block_number: int = 0, timestamp: int = 0 + ) -> Optional[int]: + """ + Starting at Prague, new payload calls must use version 4 + """ + return 4 + + @classmethod + def engine_forkchoice_updated_version( + cls, block_number: int = 0, timestamp: int = 0 + ) -> Optional[int]: + """ + At Prague, version number of NewPayload and ForkchoiceUpdated diverge. + """ + return 3 + + +class CancunEIP7692( # noqa: SC200 + Cancun, + transition_tool_name="Prague", # Evmone enables (only) EOF at Prague + blockchain_test_network_name="Prague", # Evmone enables (only) EOF at Prague + solc_name="cancun", +): + """ + Cancun + EIP-7692 (EOF) fork + """ + + @classmethod + def is_deployed(cls) -> bool: + """ + Flags that the fork has not been deployed to mainnet; it is under active + development. + """ + return False + + @classmethod + def solc_min_version(cls) -> Version: + """ + Returns the minimum version of solc that supports this fork. + """ + return Version.parse("1.0.0") # set a high version; currently unknown diff --git a/src/ethereum_test_forks/forks/history_contract.bin b/src/ethereum_test_forks/forks/history_contract.bin new file mode 100644 index 0000000000..250b34be25 Binary files /dev/null and b/src/ethereum_test_forks/forks/history_contract.bin differ diff --git a/src/ethereum_test_forks/forks/transition.py b/src/ethereum_test_forks/forks/transition.py index b32b4e7252..76b9a25eb1 100644 --- a/src/ethereum_test_forks/forks/transition.py +++ b/src/ethereum_test_forks/forks/transition.py @@ -2,7 +2,7 @@ List of all transition fork definitions. """ from ..transition_base_fork import transition_fork -from .forks import Berlin, Cancun, London, Paris, Shanghai +from .forks import Berlin, Cancun, London, Paris, Prague, Shanghai # Transition Forks @@ -31,3 +31,12 @@ class ShanghaiToCancunAtTime15k(Shanghai): """ pass + + +@transition_fork(to_fork=Prague, at_timestamp=15_000) +class CancunToPragueAtTime15k(Cancun): + """ + Cancun to Prague transition at Timestamp 15k + """ + + pass diff --git a/src/ethereum_test_forks/forks/withdrawal_request.bin b/src/ethereum_test_forks/forks/withdrawal_request.bin new file mode 100644 index 0000000000..426247e951 Binary files /dev/null and b/src/ethereum_test_forks/forks/withdrawal_request.bin differ diff --git a/src/ethereum_test_forks/tests/test_forks.py b/src/ethereum_test_forks/tests/test_forks.py index a48e249544..36a39c99e1 100644 --- a/src/ethereum_test_forks/tests/test_forks.py +++ b/src/ethereum_test_forks/tests/test_forks.py @@ -72,7 +72,7 @@ def test_forks_from(): # noqa: D103 assert forks_from(Paris, deployed_only=True)[0] == Paris assert forks_from(Paris, deployed_only=True)[-1] == LAST_DEPLOYED assert forks_from(Paris, deployed_only=False)[0] == Paris - assert forks_from(Paris, deployed_only=False)[-1] == LAST_DEVELOPMENT + # assert forks_from(Paris, deployed_only=False)[-1] == LAST_DEVELOPMENT # Too flaky def test_forks(): @@ -152,11 +152,7 @@ def test_forks(): def test_get_forks(): # noqa: D103 all_forks = get_forks() assert all_forks[0] == FIRST_DEPLOYED - assert all_forks[-1] == LAST_DEVELOPMENT - - -def test_development_forks(): # noqa: D103 - assert get_development_forks() == DEVELOPMENT_FORKS + # assert all_forks[-1] == LAST_DEVELOPMENT # Too flaky def test_deployed_forks(): # noqa: D103 diff --git a/src/ethereum_test_tools/__init__.py b/src/ethereum_test_tools/__init__.py index 5d4394060b..c382556121 100644 --- a/src/ethereum_test_tools/__init__.py +++ b/src/ethereum_test_tools/__init__.py @@ -19,6 +19,7 @@ Account, Address, Alloc, + DepositRequest, EngineAPIError, Environment, Hash, @@ -31,11 +32,12 @@ TestPrivateKey2, Transaction, Withdrawal, + WithdrawalRequest, add_kzg_version, ceiling_division, compute_create2_address, - compute_create3_address, compute_create_address, + compute_eofcreate_address, copy_opcode_cost, cost_memory_bytes, eip_2028_transaction_data_cost, @@ -48,6 +50,8 @@ BaseTest, BlockchainTest, BlockchainTestFiller, + EOFStateTest, + EOFStateTestFiller, EOFTest, EOFTestFiller, FixtureCollector, @@ -56,7 +60,7 @@ TestInfo, ) from .spec.blockchain.types import Block, Header -from .vm import Macro, Opcode, OpcodeCallArg, Opcodes +from .vm import Macro, Macros, Opcode, OpcodeCallArg, Opcodes __all__ = ( "SPEC_TYPES", @@ -75,9 +79,12 @@ "Code", "CodeGasMeasure", "Conditional", + "DepositRequest", "EngineAPIError", "Environment", "EOFException", + "EOFStateTest", + "EOFStateTestFiller", "EOFTest", "EOFTestFiller", "FixtureCollector", @@ -85,6 +92,7 @@ "Header", "Initcode", "Macro", + "Macros", "Opcode", "OpcodeCallArg", "Opcodes", @@ -104,13 +112,14 @@ "Transaction", "TransactionException", "Withdrawal", + "WithdrawalRequest", "Yul", "YulCompiler", "add_kzg_version", "ceiling_division", "compute_create_address", "compute_create2_address", - "compute_create3_address", + "compute_eofcreate_address", "copy_opcode_cost", "cost_memory_bytes", "eip_2028_transaction_data_cost", diff --git a/src/ethereum_test_tools/common/__init__.py b/src/ethereum_test_tools/common/__init__.py index 939a409aa4..d953fb6111 100644 --- a/src/ethereum_test_tools/common/__init__.py +++ b/src/ethereum_test_tools/common/__init__.py @@ -28,8 +28,8 @@ add_kzg_version, ceiling_division, compute_create2_address, - compute_create3_address, compute_create_address, + compute_eofcreate_address, copy_opcode_cost, cost_memory_bytes, eip_2028_transaction_data_cost, @@ -39,11 +39,14 @@ AccessList, Account, Alloc, + DepositRequest, Environment, Removable, + Requests, Storage, Transaction, Withdrawal, + WithdrawalRequest, ) __all__ = ( @@ -55,6 +58,7 @@ "Alloc", "Bloom", "Bytes", + "DepositRequest", "EngineAPIError", "EmptyOmmersRoot", "EmptyTrieRoot", @@ -64,6 +68,7 @@ "HexNumber", "Number", "Removable", + "Requests", "Storage", "TestAddress", "TestAddress2", @@ -72,12 +77,13 @@ "TestPrivateKey2", "Transaction", "Withdrawal", + "WithdrawalRequest", "ZeroPaddedHexNumber", "add_kzg_version", "ceiling_division", "compute_create_address", "compute_create2_address", - "compute_create3_address", + "compute_eofcreate_address", "copy_opcode_cost", "cost_memory_bytes", "eip_2028_transaction_data_cost", diff --git a/src/ethereum_test_tools/common/base_types.py b/src/ethereum_test_tools/common/base_types.py index 23e90590f0..c3f0d255d3 100644 --- a/src/ethereum_test_tools/common/base_types.py +++ b/src/ethereum_test_tools/common/base_types.py @@ -302,3 +302,19 @@ class HeaderNonce(FixedSizeBytes[8]): # type: ignore """ pass + + +class BLSPublicKey(FixedSizeBytes[48]): # type: ignore + """ + Class that helps represent BLS public keys in tests. + """ + + pass + + +class BLSSignature(FixedSizeBytes[96]): # type: ignore + """ + Class that helps represent BLS signatures in tests. + """ + + pass diff --git a/src/ethereum_test_tools/common/helpers.py b/src/ethereum_test_tools/common/helpers.py index 2d8c9b8b53..c463b18661 100644 --- a/src/ethereum_test_tools/common/helpers.py +++ b/src/ethereum_test_tools/common/helpers.py @@ -71,14 +71,13 @@ def copy_opcode_cost(length: int) -> int: return 3 + (ceiling_division(length, 32) * 3) + cost_memory_bytes(length, 0) -def compute_create3_address( +def compute_eofcreate_address( address: FixedSizeBytesConvertible, salt: FixedSizeBytesConvertible, init_container: BytesConvertible, ) -> Address: """ - Compute address of the resulting contract created using the `CREATE3` - opcode. + Compute address of the resulting contract created using the `EOFCREATE` opcode. """ hash = keccak256(b"\xff" + Address(address) + Hash(salt) + keccak256(Bytes(init_container))) return Address(hash[-20:]) diff --git a/src/ethereum_test_tools/common/types.py b/src/ethereum_test_tools/common/types.py index 0168338d72..293130ee69 100644 --- a/src/ethereum_test_tools/common/types.py +++ b/src/ethereum_test_tools/common/types.py @@ -45,6 +45,8 @@ from .base_types import ( Address, Bloom, + BLSPublicKey, + BLSSignature, Bytes, Hash, HashInt, @@ -167,7 +169,7 @@ def __init__(self, key: int, *args): def __str__(self): """Print exception string""" - return "key {0} not found in storage".format(Storage.key_value_to_string(self.key)) + return "key {0} not found in storage".format(Hash(self.key)) @dataclass(kw_only=True) class KeyValueMismatch(Exception): @@ -192,9 +194,9 @@ def __str__(self): """Print exception string""" return ( f"incorrect value in address {self.address} for " - + f"key {Storage.key_value_to_string(self.key)}:" - + f" want {Storage.key_value_to_string(self.want)} (dec:{self.want})," - + f" got {Storage.key_value_to_string(self.got)} (dec:{self.got})" + + f"key {Hash(self.key)}:" + + f" want {HexNumber(self.want)} (dec:{self.want})," + + f" got {HexNumber(self.got)} (dec:{self.got})" ) def __contains__(self, key: StorageKeyValueTypeConvertible | StorageKeyValueType) -> bool: @@ -250,7 +252,7 @@ def keys(self) -> set[StorageKeyValueType]: return set(self.root.keys()) def store_next( - self, value: StorageKeyValueTypeConvertible | StorageKeyValueType + self, value: StorageKeyValueTypeConvertible | StorageKeyValueType | bool ) -> StorageKeyValueType: """ Stores a value in the storage and returns the key where the value is stored. @@ -1240,26 +1242,175 @@ def list_blob_versioned_hashes(input_txs: List["Transaction"]) -> List[Hash]: ] +class RequestBase: + """ + Base class for requests. + """ + + @classmethod + def type_byte(cls) -> bytes: + """ + Returns the request type. + """ + raise NotImplementedError("request_type must be implemented in child classes") + + def to_serializable_list(self) -> List[Any]: + """ + Returns the request's attributes as a list of serializable elements. + """ + raise NotImplementedError("to_serializable_list must be implemented in child classes") + + +class DepositRequestGeneric(RequestBase, CamelModel, Generic[NumberBoundTypeVar]): + """ + Generic deposit type used as a parent for DepositRequest and FixtureDepositRequest. + """ + + pubkey: BLSPublicKey + withdrawal_credentials: Hash + amount: NumberBoundTypeVar + signature: BLSSignature + index: NumberBoundTypeVar + + @classmethod + def type_byte(cls) -> bytes: + """ + Returns the deposit request type. + """ + return b"\0" + + def to_serializable_list(self) -> List[Any]: + """ + Returns the deposit's attributes as a list of serializable elements. + """ + return [ + self.pubkey, + self.withdrawal_credentials, + Uint(self.amount), + self.signature, + Uint(self.index), + ] + + +class DepositRequest(DepositRequestGeneric[HexNumber]): + """ + Deposit Request type + """ + + pass + + +class WithdrawalRequestGeneric(RequestBase, CamelModel, Generic[NumberBoundTypeVar]): + """ + Generic withdrawal request type used as a parent for WithdrawalRequest and + FixtureWithdrawalRequest. + """ + + source_address: Address = Address(0) + validator_public_key: BLSPublicKey + amount: NumberBoundTypeVar + + @classmethod + def type_byte(cls) -> bytes: + """ + Returns the withdrawal request type. + """ + return b"\1" + + def to_serializable_list(self) -> List[Any]: + """ + Returns the deposit's attributes as a list of serializable elements. + """ + return [ + self.source_address, + self.validator_public_key, + Uint(self.amount), + ] + + +class WithdrawalRequest(WithdrawalRequestGeneric[HexNumber]): + """ + Withdrawal Request type + """ + + pass + + +class Requests(RootModel[List[DepositRequest | WithdrawalRequest]]): + """ + Requests for the transition tool. + """ + + root: List[DepositRequest | WithdrawalRequest] = Field(default_factory=list) + + def to_serializable_list(self) -> List[Any]: + """ + Returns the requests as a list of serializable elements. + """ + return [r.type_byte() + eth_rlp.encode(r.to_serializable_list()) for r in self.root] + + @cached_property + def trie_root(self) -> Hash: + """ + Returns the root hash of the requests. + """ + t = HexaryTrie(db={}) + for i, r in enumerate(self.root): + t.set( + eth_rlp.encode(Uint(i)), + r.type_byte() + eth_rlp.encode(r.to_serializable_list()), + ) + return Hash(t.root_hash) + + def deposit_requests(self) -> List[DepositRequest]: + """ + Returns the list of deposit requests. + """ + return [d for d in self.root if isinstance(d, DepositRequest)] + + def withdrawal_requests(self) -> List[WithdrawalRequest]: + """ + Returns the list of withdrawal requests. + """ + return [w for w in self.root if isinstance(w, WithdrawalRequest)] + + # TODO: Move to other file # Transition tool models +class TransactionLog(CamelModel): + """ + Transaction log + """ + + address: Address + topics: List[Hash] + data: Bytes + block_number: HexNumber + transaction_hash: Hash + transaction_index: HexNumber + block_hash: Hash + log_index: HexNumber + removed: bool + + class TransactionReceipt(CamelModel): """ Transaction receipt """ - root: Bytes - status: HexNumber - cumulative_gas_used: HexNumber - logs_bloom: Bloom - logs: List[Dict[str, str]] | None = None transaction_hash: Hash - contract_address: Address gas_used: HexNumber + root: Bytes | None = None + status: HexNumber | None = None + cumulative_gas_used: HexNumber | None = None + logs_bloom: Bloom | None = None + logs: List[TransactionLog] | None = None + contract_address: Address | None = None effective_gas_price: HexNumber | None = None - block_hash: Hash - transaction_index: HexNumber + block_hash: Hash | None = None + transaction_index: HexNumber | None = None blob_gas_used: HexNumber | None = None blob_gas_price: HexNumber | None = None @@ -1294,6 +1445,9 @@ class Result(CamelModel): withdrawals_root: Hash | None = None excess_blob_gas: HexNumber | None = Field(None, alias="currentExcessBlobGas") blob_gas_used: HexNumber | None = None + requests_root: Hash | None = None + deposit_requests: List[DepositRequest] | None = None + withdrawal_requests: List[WithdrawalRequest] | None = None class TransitionToolOutput(CamelModel): diff --git a/src/ethereum_test_tools/eof/v1/__init__.py b/src/ethereum_test_tools/eof/v1/__init__.py index c9cc85ecd3..66eb8c913f 100644 --- a/src/ethereum_test_tools/eof/v1/__init__.py +++ b/src/ethereum_test_tools/eof/v1/__init__.py @@ -121,6 +121,22 @@ class Section(CopyValidateModel): Whether to automatically compute the best suggestion for the code_inputs, code_outputs values for this code section. """ + skip_header_listing: bool = False + """ + Skip section from listing in the header + """ + skip_body_listing: bool = False + """ + Skip section from listing in the body + """ + skip_types_body_listing: bool = False + """ + Skip section from listing in the types body (input, output, stack) bytes + """ + skip_types_header_listing: bool = False + """ + Skip section from listing in the types header (not calculating input, output, stack size) + """ @cached_property def header(self) -> bytes: @@ -197,8 +213,18 @@ def list_header(sections: List["Section"]) -> bytes: return b"".join(s.header for s in sections) h = sections[0].kind.to_bytes(HEADER_SECTION_KIND_BYTE_LENGTH, "big") - h += len(sections).to_bytes(HEADER_SECTION_COUNT_BYTE_LENGTH, "big") + + # Count only those sections that are not marked to be skipped for header calculation + header_registered_sections = 0 + for cs in sections: + if not cs.skip_header_listing: + header_registered_sections += 1 + + h += header_registered_sections.to_bytes(HEADER_SECTION_COUNT_BYTE_LENGTH, "big") for cs in sections: + # If section is marked to skip the header calculation, don't make header for it + if cs.skip_header_listing: + continue size = cs.custom_size if "custom_size" in cs.model_fields_set else len(cs.data) h += size.to_bytes(HEADER_SECTION_SIZE_BYTE_LENGTH, "big") @@ -333,8 +359,20 @@ def bytecode(self) -> bytes: # Add type section if needed if self.auto_type_section.any() and count_sections(sections, SectionKind.TYPE) == 0: - type_section_data = b"".join(s.type_definition for s in sections) - sections = [Section(kind=SectionKind.TYPE, data=type_section_data)] + sections + # Calculate skipping flags + types_header_size = 0 + type_section_data = b"" + for s in sections: + types_header_size += ( + len(s.type_definition) if not s.skip_types_header_listing else 0 + ) + type_section_data += s.type_definition if not s.skip_types_body_listing else b"" + + sections = [ + Section( + kind=SectionKind.TYPE, data=type_section_data, custom_size=types_header_size + ) + ] + sections # Add data section if needed if self.auto_data_section and count_sections(sections, SectionKind.DATA) == 0: @@ -371,7 +409,7 @@ def bytecode(self) -> bytes: for s in body_sections: if s.kind == SectionKind.TYPE and self.auto_type_section == AutoSection.ONLY_HEADER: continue - if s.data: + if s.data and not s.skip_body_listing: c += s.data # Add extra (garbage) @@ -403,14 +441,12 @@ def init_container(self) -> Container: """ return Container( sections=[ - Section( - kind=SectionKind.CODE, - data=Op.RETURNCONTRACT(0, 0, 0), + Section.Code( + code=Op.RETURNCONTRACT[0](0, 0), max_stack_height=2, ), - Section( - kind=SectionKind.CONTAINER, - data=bytes(self.deploy_container), + Section.Container( + container=self.deploy_container, ), ], ) @@ -418,19 +454,17 @@ def init_container(self) -> Container: @cached_property def bytecode(self) -> bytes: """ - Generate legacy initcode that inits a contract with the specified code. - The initcode can be padded to a specified length for testing purposes. + Generate an EOF container performs `EOFCREATE` with the specified code. """ initcode = Container( sections=[ - Section( - data=Op.CREATE3(0, 0, 0, 0, len(self.deploy_container)) + Op.STOP(), - kind=SectionKind.CODE, + Section.Code( + # TODO: Pass calldata + code=Op.EOFCREATE[0](0, 0, 0, 0) + Op.STOP(), max_stack_height=4, ), - Section( - kind=SectionKind.CONTAINER, - data=self.init_container, + Section.Container( + container=self.init_container, ), ] ) diff --git a/src/ethereum_test_tools/exceptions/__init__.py b/src/ethereum_test_tools/exceptions/__init__.py index 05e6d41ae0..377677dfcb 100644 --- a/src/ethereum_test_tools/exceptions/__init__.py +++ b/src/ethereum_test_tools/exceptions/__init__.py @@ -2,6 +2,7 @@ Exceptions for invalid execution. """ +from .evmone_exceptions import EvmoneExceptionMapper from .exceptions import ( BlockException, BlockExceptionInstanceOrList, @@ -18,4 +19,5 @@ "ExceptionInstanceOrList", "TransactionException", "TransactionExceptionInstanceOrList", + "EvmoneExceptionMapper", ] diff --git a/src/ethereum_test_tools/exceptions/evmone_exceptions.py b/src/ethereum_test_tools/exceptions/evmone_exceptions.py new file mode 100644 index 0000000000..a51d65c995 --- /dev/null +++ b/src/ethereum_test_tools/exceptions/evmone_exceptions.py @@ -0,0 +1,99 @@ +""" +Evmone eof exceptions ENUM -> str mapper +""" + +from dataclasses import dataclass + +from bidict import frozenbidict + +from .exceptions import EOFException + + +@dataclass +class ExceptionMessage: + """Defines a mapping between an exception and a message.""" + + exception: EOFException + message: str + + +class EvmoneExceptionMapper: + """ + Translate between EEST exceptions and error strings returned by evmone. + """ + + _mapping_data = ( + # TODO EVMONE needs to differentiate when the section is missing in the header or body + ExceptionMessage(EOFException.MISSING_STOP_OPCODE, "err: no_terminating_instruction"), + ExceptionMessage(EOFException.MISSING_CODE_HEADER, "err: code_section_missing"), + ExceptionMessage(EOFException.MISSING_TYPE_HEADER, "err: type_section_missing"), + # TODO EVMONE these exceptions are too similar, this leeds to ambiguity + ExceptionMessage(EOFException.MISSING_TERMINATOR, "err: header_terminator_missing"), + ExceptionMessage( + EOFException.MISSING_HEADERS_TERMINATOR, "err: section_headers_not_terminated" + ), + ExceptionMessage(EOFException.INVALID_VERSION, "err: eof_version_unknown"), + ExceptionMessage( + EOFException.INVALID_NON_RETURNING_FLAG, "err: invalid_non_returning_flag" + ), + ExceptionMessage(EOFException.INVALID_MAGIC, "err: invalid_prefix"), + ExceptionMessage( + EOFException.INVALID_FIRST_SECTION_TYPE, "err: invalid_first_section_type" + ), + ExceptionMessage( + EOFException.INVALID_SECTION_BODIES_SIZE, "err: invalid_section_bodies_size" + ), + ExceptionMessage(EOFException.INVALID_TYPE_SECTION_SIZE, "err: invalid_type_section_size"), + ExceptionMessage(EOFException.INCOMPLETE_SECTION_SIZE, "err: incomplete_section_size"), + ExceptionMessage(EOFException.INCOMPLETE_SECTION_NUMBER, "err: incomplete_section_number"), + ExceptionMessage(EOFException.TOO_MANY_CODE_SECTIONS, "err: too_many_code_sections"), + ExceptionMessage(EOFException.ZERO_SECTION_SIZE, "err: zero_section_size"), + ExceptionMessage(EOFException.MISSING_DATA_SECTION, "err: data_section_missing"), + ExceptionMessage(EOFException.UNDEFINED_INSTRUCTION, "err: undefined_instruction"), + ExceptionMessage( + EOFException.INPUTS_OUTPUTS_NUM_ABOVE_LIMIT, "err: inputs_outputs_num_above_limit" + ), + ExceptionMessage(EOFException.UNREACHABLE_INSTRUCTIONS, "err: unreachable_instructions"), + ExceptionMessage(EOFException.INVALID_RJUMP_DESTINATION, "err: invalid_rjump_destination"), + ExceptionMessage(EOFException.UNREACHABLE_CODE_SECTIONS, "err: unreachable_code_sections"), + ExceptionMessage(EOFException.STACK_UNDERFLOW, "err: stack_underflow"), + ExceptionMessage( + EOFException.MAX_STACK_HEIGHT_ABOVE_LIMIT, "err: max_stack_height_above_limit" + ), + ExceptionMessage( + EOFException.STACK_HIGHER_THAN_OUTPUTS, "err: stack_higher_than_outputs_required" + ), + ExceptionMessage( + EOFException.JUMPF_DESTINATION_INCOMPATIBLE_OUTPUTS, + "err: jumpf_destination_incompatible_outputs", + ), + ExceptionMessage(EOFException.INVALID_MAX_STACK_HEIGHT, "err: invalid_max_stack_height"), + ExceptionMessage(EOFException.INVALID_DATALOADN_INDEX, "err: invalid_dataloadn_index"), + ) + + def __init__(self) -> None: + assert len(set(entry.exception for entry in self._mapping_data)) == len( + self._mapping_data + ), "Duplicate exception in _mapping_data" + assert len(set(entry.message for entry in self._mapping_data)) == len( + self._mapping_data + ), "Duplicate message in _mapping_data" + self.exception_to_message_map: frozenbidict = frozenbidict( + {entry.exception: entry.message for entry in self._mapping_data} + ) + + def exception_to_message(self, exception: EOFException) -> str: + """Takes an EOFException and returns a formatted string.""" + message = self.exception_to_message_map.get( + exception, + f"No message defined for {exception}; please add it to {self.__class__.__name__}", + ) + return message + + def message_to_exception(self, exception_string: str) -> EOFException: + """Takes a string and tries to find matching exception""" + # TODO inform tester where to add the missing exception if get uses default + exception = self.exception_to_message_map.inverse.get( + exception_string, EOFException.UNDEFINED_EXCEPTION + ) + return exception diff --git a/src/ethereum_test_tools/exceptions/exceptions.py b/src/ethereum_test_tools/exceptions/exceptions.py index 75372fa67a..5880f50b80 100644 --- a/src/ethereum_test_tools/exceptions/exceptions.py +++ b/src/ethereum_test_tools/exceptions/exceptions.py @@ -180,6 +180,10 @@ class BlockException(ExceptionBase): """ Block's rlp encoding is valid but ethereum structures in it are invalid """ + INVALID_REQUESTS = auto() + """ + Block's requests are invalid + """ @unique @@ -193,6 +197,16 @@ class EOFException(ExceptionBase): Expect some exception, not yet known """ + UNDEFINED_EXCEPTION = auto() + """ + Indicates that exception string is not mapped to an exception enum + """ + + UNDEFINED_INSTRUCTION = auto() + """ + EOF container has undefined instruction in it's body code + """ + UNKNOWN_VERSION = auto() """ EOF container has an unknown version @@ -209,11 +223,19 @@ class EOFException(ExceptionBase): """ EOF container version bytes mismatch """ + INVALID_NON_RETURNING_FLAG = auto() + """ + EOF container's section has non-returning flag set incorrectly + """ + INVALID_RJUMP_DESTINATION = auto() + """ + Code has RJUMP instruction with invalid parameters + """ MISSING_TYPE_HEADER = auto() """ EOF container missing types section """ - INVALID_TYPE_SIZE = auto() + INVALID_TYPE_SECTION_SIZE = auto() """ EOF container types section has wrong size """ @@ -241,6 +263,10 @@ class EOFException(ExceptionBase): """ EOF container data header construction is wrong """ + MISSING_DATA_SECTION = auto() + """ + EOF container missing data section + """ INCOMPLETE_CONTAINER = auto() """ EOF container bytes are incomplete @@ -277,6 +303,47 @@ class EOFException(ExceptionBase): """ EOF container header has too many code sections """ + MISSING_STOP_OPCODE = auto() + """ + EOF container's code missing STOP bytecode at it's end + """ + INPUTS_OUTPUTS_NUM_ABOVE_LIMIT = auto() + """ + EOF container code section inputs/outputs number is above the limit + """ + UNREACHABLE_INSTRUCTIONS = auto() + """ + EOF container's code have instructions that are unreachable + """ + UNREACHABLE_CODE_SECTIONS = auto() + """ + EOF container's body have code sections that are unreachable + """ + STACK_UNDERFLOW = auto() + """ + EOF container's code produces an stack underflow + """ + MAX_STACK_HEIGHT_ABOVE_LIMIT = auto() + """ + EOF container's specified max stack height is above the limit + """ + STACK_HIGHER_THAN_OUTPUTS = auto() + """ + EOF container section stack height is higher than the outputs + when returning + """ + JUMPF_DESTINATION_INCOMPATIBLE_OUTPUTS = auto() + """ + EOF container section JUMPF's to a destination section with incompatible outputs + """ + INVALID_MAX_STACK_HEIGHT = auto() + """ + EOF container section's specified max stack height does not match the actual stack height + """ + INVALID_DATALOADN_INDEX = auto() + """ + A DATALOADN instruction has out-of-bounds index for the data section + """ """ diff --git a/src/ethereum_test_tools/rpc/__init__.py b/src/ethereum_test_tools/rpc/__init__.py new file mode 100644 index 0000000000..2dec4c2fcf --- /dev/null +++ b/src/ethereum_test_tools/rpc/__init__.py @@ -0,0 +1,7 @@ +""" +JSON-RPC methods and helper functions for EEST consume based hive simulators. +""" + +from .rpc import BlockNumberType, EthRPC + +__all__ = ["EthRPC", "BlockNumberType"] diff --git a/src/ethereum_test_tools/rpc/rpc.py b/src/ethereum_test_tools/rpc/rpc.py new file mode 100644 index 0000000000..8b6393482f --- /dev/null +++ b/src/ethereum_test_tools/rpc/rpc.py @@ -0,0 +1,115 @@ +""" +JSON-RPC methods and helper functions for EEST consume based hive simulators. +""" + +from abc import ABC +from typing import Any, Dict, List, Literal, Optional, Union + +import requests +from tenacity import retry, stop_after_attempt, wait_exponential + +from ethereum_test_tools import Address + +BlockNumberType = Union[int, Literal["latest", "earliest", "pending"]] + + +class BaseRPC(ABC): + """ + Represents a base RPC class for every RPC call used within EEST based hive simulators. + """ + + def __init__(self, client_ip: str, port: int): + self.ip = client_ip + self.url = f"http://{client_ip}:{port}" + + @retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, max=10)) + def post_request( + self, method: str, params: List[Any], extra_headers: Optional[Dict] = None + ) -> Dict: + """ + Sends a JSON-RPC POST request to the client RPC server at port defined in the url. + """ + payload = { + "jsonrpc": "2.0", + "method": method, + "params": params, + "id": 1, + } + base_header = { + "Content-Type": "application/json", + } + headers = base_header if extra_headers is None else {**base_header, **extra_headers} + + response = requests.post(self.url, json=payload, headers=headers) + response.raise_for_status() + result = response.json().get("result") + + if result is None or "error" in result: + error_info = "result is None; and therefore contains no error info" + error_code = None + if result is not None: + error_info = result["error"] + error_code = result["error"]["code"] + raise Exception( + f"Error calling JSON RPC {method}, code: {error_code}, " f"message: {error_info}" + ) + + return result + + +class EthRPC(BaseRPC): + """ + Represents an `eth_X` RPC class for every default ethereum RPC method used within EEST based + hive simulators. + """ + + def __init__(self, client_ip): + """ + Initializes the EthRPC class with the http port 8545, which requires no authentication. + """ + super().__init__(client_ip, port=8545) + + BlockNumberType = Union[int, Literal["latest", "earliest", "pending"]] + + def get_block_by_number(self, block_number: BlockNumberType = "latest", full_txs: bool = True): + """ + `eth_getBlockByNumber`: Returns information about a block by block number. + """ + block = hex(block_number) if isinstance(block_number, int) else block_number + return self.post_request("eth_getBlockByNumber", [block, full_txs]) + + def get_balance(self, address: str, block_number: BlockNumberType = "latest"): + """ + `eth_getBalance`: Returns the balance of the account of given address. + """ + block = hex(block_number) if isinstance(block_number, int) else block_number + return self.post_request("eth_getBalance", [address, block]) + + def get_transaction_count(self, address: Address, block_number: BlockNumberType = "latest"): + """ + `eth_getTransactionCount`: Returns the number of transactions sent from an address. + """ + block = hex(block_number) if isinstance(block_number, int) else block_number + return self.post_request("eth_getTransactionCount", [address, block]) + + def get_storage_at( + self, address: str, position: str, block_number: BlockNumberType = "latest" + ): + """ + `eth_getStorageAt`: Returns the value from a storage position at a given address. + """ + block = hex(block_number) if isinstance(block_number, int) else block_number + return self.post_request("eth_getStorageAt", [address, position, block]) + + def storage_at_keys( + self, account: str, keys: List[str], block_number: BlockNumberType = "latest" + ) -> Dict: + """ + Helper to retrieve the storage values for the specified keys at a given address and block + number. + """ + results: Dict = {} + for key in keys: + storage_value = self.get_storage_at(account, key, block_number) + results[key] = storage_value + return results diff --git a/src/ethereum_test_tools/spec/__init__.py b/src/ethereum_test_tools/spec/__init__.py index ea2f1b2178..5ebe48aefa 100644 --- a/src/ethereum_test_tools/spec/__init__.py +++ b/src/ethereum_test_tools/spec/__init__.py @@ -6,11 +6,24 @@ from .base.base_test import BaseFixture, BaseTest, TestSpec from .blockchain.blockchain_test import BlockchainTest, BlockchainTestFiller, BlockchainTestSpec -from .eof.eof_test import EOFTest, EOFTestFiller, EOFTestSpec +from .eof.eof_test import ( + EOFStateTest, + EOFStateTestFiller, + EOFStateTestSpec, + EOFTest, + EOFTestFiller, + EOFTestSpec, +) from .fixture_collector import FixtureCollector, TestInfo from .state.state_test import StateTest, StateTestFiller, StateTestOnly, StateTestSpec -SPEC_TYPES: List[Type[BaseTest]] = [BlockchainTest, StateTest, StateTestOnly, EOFTest] +SPEC_TYPES: List[Type[BaseTest]] = [ + BlockchainTest, + StateTest, + StateTestOnly, + EOFTest, + EOFStateTest, +] __all__ = ( "SPEC_TYPES", @@ -19,6 +32,9 @@ "BlockchainTest", "BlockchainTestFiller", "BlockchainTestSpec", + "EOFStateTest", + "EOFStateTestFiller", + "EOFStateTestSpec", "EOFTest", "EOFTestFiller", "EOFTestSpec", diff --git a/src/ethereum_test_tools/spec/base/base_test.py b/src/ethereum_test_tools/spec/base/base_test.py index 911f38fe4e..5001508b44 100644 --- a/src/ethereum_test_tools/spec/base/base_test.py +++ b/src/ethereum_test_tools/spec/base/base_test.py @@ -111,6 +111,12 @@ def fill_info( if ref_spec is not None: ref_spec.write_info(self.info) + def get_fork(self) -> str: + """ + Returns the fork of the fixture as a string. + """ + raise NotImplementedError + class BaseTest(BaseModel): """ @@ -147,8 +153,6 @@ def pytest_parameter_name(cls) -> str: By default, it returns the underscore separated name of the class. """ - if cls.__name__ == "EOFTest": - return "eof_test" return reduce(lambda x, y: x + ("_" if y.isupper() else "") + y, cls.__name__).lower() def get_next_transition_tool_output_path(self) -> str: diff --git a/src/ethereum_test_tools/spec/blockchain/blockchain_test.py b/src/ethereum_test_tools/spec/blockchain/blockchain_test.py index d9e61ce1f7..2cd1366d02 100644 --- a/src/ethereum_test_tools/spec/blockchain/blockchain_test.py +++ b/src/ethereum_test_tools/spec/blockchain/blockchain_test.py @@ -10,10 +10,10 @@ from ethereum_test_forks import Fork from evm_transition_tool import FixtureFormats, TransitionTool -from ...common import Alloc, EmptyTrieRoot, Environment, Hash, Transaction, Withdrawal +from ...common import Alloc, EmptyTrieRoot, Environment, Hash, Requests, Transaction, Withdrawal from ...common.constants import EmptyOmmersRoot from ...common.json import to_json -from ...common.types import TransitionToolOutput +from ...common.types import DepositRequest, TransitionToolOutput, WithdrawalRequest from ..base.base_test import BaseFixture, BaseTest, verify_result, verify_transactions from ..debugging import print_traces from .types import ( @@ -22,10 +22,12 @@ Fixture, FixtureBlock, FixtureBlockBase, + FixtureDepositRequest, FixtureEngineNewPayload, FixtureHeader, FixtureTransaction, FixtureWithdrawal, + FixtureWithdrawalRequest, HiveFixture, InvalidFixtureBlock, ) @@ -138,6 +140,9 @@ def make_genesis( if env.withdrawals is not None else None, parent_beacon_block_root=env.parent_beacon_block_root, + requests_root=Requests(root=[]).trie_root + if fork.header_requests_required(0, 0) + else None, ) return ( @@ -145,7 +150,11 @@ def make_genesis( FixtureBlockBase( header=genesis, withdrawals=None if env.withdrawals is None else [], - ).with_rlp(txs=[]), + deposit_requests=[] if fork.header_requests_required(0, 0) else None, + withdrawal_requests=[] if fork.header_requests_required(0, 0) else None, + ).with_rlp( + txs=[], requests=Requests() if fork.header_requests_required(0, 0) else None + ), ) def generate_block_data( @@ -156,7 +165,7 @@ def generate_block_data( previous_env: Environment, previous_alloc: Alloc, eips: Optional[List[int]] = None, - ) -> Tuple[FixtureHeader, List[Transaction], Alloc, Environment]: + ) -> Tuple[FixtureHeader, List[Transaction], Requests | None, Alloc, Environment]: """ Generate common block data for both make_fixture and make_hive_fixture. """ @@ -248,7 +257,33 @@ def generate_block_data( # transition tool processing. header = header.join(block.rlp_modifier) - return header, txs, transition_tool_output.alloc, env + requests = None + if fork.header_requests_required(header.number, header.timestamp): + requests_list: List[DepositRequest | WithdrawalRequest] = [] + if transition_tool_output.result.deposit_requests is not None: + requests_list += transition_tool_output.result.deposit_requests + if transition_tool_output.result.withdrawal_requests is not None: + requests_list += transition_tool_output.result.withdrawal_requests + requests = Requests(root=requests_list) + + if requests is not None and requests.trie_root != header.requests_root: + raise Exception( + f"Requests root in header does not match the requests root in the transition tool " + "output: " + f"{header.requests_root} != {requests.trie_root}" + ) + + if block.requests is not None: + requests = Requests(root=block.requests) + header.requests_root = requests.trie_root + + return ( + header, + txs, + requests, + transition_tool_output.alloc, + env, + ) def network_info(self, fork: Fork, eips: Optional[List[int]] = None): """ @@ -292,7 +327,7 @@ def make_fixture( # This is the most common case, the RLP needs to be constructed # based on the transactions to be included in the block. # Set the environment according to the block to execute. - header, txs, new_alloc, new_env = self.generate_block_data( + header, txs, requests, new_alloc, new_env = self.generate_block_data( t8n=t8n, fork=fork, block=block, @@ -307,7 +342,19 @@ def make_fixture( withdrawals=[FixtureWithdrawal.from_withdrawal(w) for w in new_env.withdrawals] if new_env.withdrawals is not None else None, - ).with_rlp(txs=txs) + deposit_requests=[ + FixtureDepositRequest.from_deposit_request(d) + for d in requests.deposit_requests() + ] + if requests is not None + else None, + withdrawal_requests=[ + FixtureWithdrawalRequest.from_withdrawal_request(w) + for w in requests.withdrawal_requests() + ] + if requests is not None + else None, + ).with_rlp(txs=txs, requests=requests) if block.exception is None: fixture_blocks.append(fixture_block) # Update env, alloc and last block hash for the next block. @@ -366,7 +413,7 @@ def make_hive_fixture( head_hash = genesis.header.block_hash for block in self.blocks: - header, txs, new_alloc, new_env = self.generate_block_data( + header, txs, requests, new_alloc, new_env = self.generate_block_data( t8n=t8n, fork=fork, block=block, previous_env=env, previous_alloc=alloc, eips=eips ) if block.rlp is None: @@ -376,6 +423,7 @@ def make_hive_fixture( header=header, transactions=txs, withdrawals=new_env.withdrawals, + requests=requests, validation_error=block.exception, error_code=block.engine_api_error_code, ) @@ -402,7 +450,7 @@ def make_hive_fixture( # Most clients require the header to start the sync process, so we create an empty # block on top of the last block of the test to send it as new payload and trigger the # sync process. - sync_header, _, _, _ = self.generate_block_data( + sync_header, _, requests, _, _ = self.generate_block_data( t8n=t8n, fork=fork, block=Block(), @@ -415,6 +463,7 @@ def make_hive_fixture( header=sync_header, transactions=[], withdrawals=[], + requests=requests, validation_error=None, error_code=None, ) diff --git a/src/ethereum_test_tools/spec/blockchain/types.py b/src/ethereum_test_tools/spec/blockchain/types.py index 8b99d1ab68..9b00d6f5c5 100644 --- a/src/ethereum_test_tools/spec/blockchain/types.py +++ b/src/ethereum_test_tools/spec/blockchain/types.py @@ -8,7 +8,14 @@ from ethereum import rlp as eth_rlp from ethereum.base_types import Uint from ethereum.crypto.hash import keccak256 -from pydantic import ConfigDict, Field, PlainSerializer, computed_field +from pydantic import ( + AliasChoices, + ConfigDict, + Field, + PlainSerializer, + computed_field, + field_validator, +) from ethereum_test_forks import Fork from evm_transition_tool import FixtureFormats @@ -27,13 +34,18 @@ from ...common.types import ( Alloc, CamelModel, + DepositRequest, + DepositRequestGeneric, Environment, Removable, + Requests, Transaction, TransactionFixtureConverter, TransactionGeneric, Withdrawal, WithdrawalGeneric, + WithdrawalRequest, + WithdrawalRequestGeneric, ) from ...exceptions import BlockException, ExceptionInstanceOrList, TransactionException from ..base.base_test import BaseFixture @@ -64,6 +76,7 @@ class Header(CamelModel): blob_gas_used: Removable | HexNumber | None = None excess_blob_gas: Removable | HexNumber | None = None parent_beacon_block_root: Removable | Hash | None = None + requests_root: Removable | Hash | None = None REMOVE_FIELD: ClassVar[Removable] = Removable() """ @@ -97,6 +110,26 @@ class Header(CamelModel): }, ) + @field_validator("withdrawals_root", mode="before") + @classmethod + def validate_withdrawals_root(cls, value): + """ + Helper validator to convert a list of withdrawals into the withdrawals root hash. + """ + if isinstance(value, list): + return Withdrawal.list_root(value) + return value + + @field_validator("requests_root", mode="before") + @classmethod + def validate_requests_root(cls, value): + """ + Helper validator to convert a list of requests into the requests root hash. + """ + if isinstance(value, list): + return Requests(root=value).trie_root + return value + class HeaderForkRequirement(str): """ @@ -138,11 +171,19 @@ class FixtureHeader(CamelModel): parent_hash: Hash ommers_hash: Hash = Field(Hash(EmptyOmmersRoot), alias="uncleHash") - fee_recipient: Address = Field(..., alias="coinbase") + fee_recipient: Address = Field( + ..., alias="coinbase", validation_alias=AliasChoices("coinbase", "miner") + ) state_root: Hash - transactions_trie: Hash - receipts_root: Hash = Field(..., alias="receiptTrie") - logs_bloom: Bloom = Field(..., alias="bloom") + transactions_trie: Hash = Field( + validation_alias=AliasChoices("transactionsTrie", "transactionsRoot") + ) + receipts_root: Hash = Field( + ..., alias="receiptTrie", validation_alias=AliasChoices("receiptTrie", "receiptsRoot") + ) + logs_bloom: Bloom = Field( + ..., alias="bloom", validation_alias=AliasChoices("bloom", "logsBloom") + ) difficulty: ZeroPaddedHexNumber = ZeroPaddedHexNumber(0) number: ZeroPaddedHexNumber gas_limit: ZeroPaddedHexNumber @@ -164,6 +205,7 @@ class FixtureHeader(CamelModel): parent_beacon_block_root: Annotated[Hash, HeaderForkRequirement("beacon_root")] | None = Field( None ) + requests_root: Annotated[Hash, HeaderForkRequirement("requests")] | None = Field(None) fork: Fork | None = Field(None, exclude=True) @@ -303,6 +345,10 @@ class Block(Header): """ List of withdrawals to perform for this block. """ + requests: List[DepositRequest | WithdrawalRequest] | None = None + """ + Custom list of requests to embed in this block. + """ def set_environment(self, env: Environment) -> Environment: """ @@ -380,13 +426,16 @@ class FixtureExecutionPayload(CamelModel): transactions: List[Bytes] withdrawals: List[Withdrawal] | None = None + deposit_requests: List[DepositRequest] | None = None + withdrawal_requests: List[WithdrawalRequest] | None = None @classmethod def from_fixture_header( cls, header: FixtureHeader, transactions: List[Transaction], - withdrawals: List[Withdrawal] | None = None, + withdrawals: List[Withdrawal] | None, + requests: Requests | None, ) -> "FixtureExecutionPayload": """ Returns a FixtureExecutionPayload from a FixtureHeader, a list @@ -396,6 +445,8 @@ def from_fixture_header( **header.model_dump(exclude={"rlp"}, exclude_none=True), transactions=[tx.rlp for tx in transactions], withdrawals=withdrawals, + deposit_requests=requests.deposit_requests() if requests is not None else None, + withdrawal_requests=requests.withdrawal_requests() if requests is not None else None, ) @@ -428,6 +479,7 @@ def from_fixture_header( header: FixtureHeader, transactions: List[Transaction], withdrawals: List[Withdrawal] | None, + requests: Requests | None, **kwargs, ) -> "FixtureEngineNewPayload": """ @@ -442,6 +494,7 @@ def from_fixture_header( header=header, transactions=transactions, withdrawals=withdrawals, + requests=requests, ), version=new_payload_version, blob_versioned_hashes=( @@ -483,6 +536,34 @@ def from_withdrawal(cls, w: WithdrawalGeneric) -> "FixtureWithdrawal": return cls(**w.model_dump()) +class FixtureDepositRequest(DepositRequestGeneric[ZeroPaddedHexNumber]): + """ + Structure to represent a single deposit request to be processed by the beacon + chain. + """ + + @classmethod + def from_deposit_request(cls, d: DepositRequestGeneric) -> "FixtureDepositRequest": + """ + Returns a FixtureDepositRequest from a DepositRequest. + """ + return cls(**d.model_dump()) + + +class FixtureWithdrawalRequest(WithdrawalRequestGeneric[ZeroPaddedHexNumber]): + """ + Structure to represent a single withdrawal request to be processed by the beacon + chain. + """ + + @classmethod + def from_withdrawal_request(cls, d: WithdrawalRequestGeneric) -> "FixtureWithdrawalRequest": + """ + Returns a FixtureWithdrawalRequest from a WithdrawalRequest. + """ + return cls(**d.model_dump()) + + class FixtureBlockBase(CamelModel): """Representation of an Ethereum block within a test Fixture without RLP bytes.""" @@ -490,6 +571,8 @@ class FixtureBlockBase(CamelModel): txs: List[FixtureTransaction] = Field(default_factory=list, alias="transactions") ommers: List[FixtureHeader] = Field(default_factory=list, alias="uncleHeaders") withdrawals: List[FixtureWithdrawal] | None = None + deposit_requests: List[FixtureDepositRequest] | None = None + withdrawal_requests: List[FixtureWithdrawalRequest] | None = None @computed_field(alias="blocknumber") # type: ignore[misc] @cached_property @@ -499,7 +582,7 @@ def block_number(self) -> Number: """ return Number(self.header.number) - def with_rlp(self, txs: List[Transaction]) -> "FixtureBlock": + def with_rlp(self, txs: List[Transaction], requests: Requests | None) -> "FixtureBlock": """ Returns a FixtureBlock with the RLP bytes set. """ @@ -512,6 +595,9 @@ def with_rlp(self, txs: List[Transaction]) -> "FixtureBlock": if self.withdrawals is not None: block.append([w.to_serializable_list() for w in self.withdrawals]) + if requests is not None: + block.append(requests.to_serializable_list()) + return FixtureBlock( **self.model_dump(), rlp=eth_rlp.encode(block), @@ -552,6 +638,12 @@ class FixtureCommon(BaseFixture): pre: Alloc post_state: Alloc + def get_fork(self) -> str: + """ + Returns the fork of the fixture as a string. + """ + return self.fork + class Fixture(FixtureCommon): """ diff --git a/src/ethereum_test_tools/spec/consume/__init__.py b/src/ethereum_test_tools/spec/consume/__init__.py new file mode 100644 index 0000000000..c6378cbf8b --- /dev/null +++ b/src/ethereum_test_tools/spec/consume/__init__.py @@ -0,0 +1,3 @@ +""" +Defines pydantic models used by the consume commands. +""" diff --git a/src/ethereum_test_tools/spec/consume/types.py b/src/ethereum_test_tools/spec/consume/types.py new file mode 100644 index 0000000000..5ad039be23 --- /dev/null +++ b/src/ethereum_test_tools/spec/consume/types.py @@ -0,0 +1,152 @@ +""" +Defines models for index files and consume test cases. +""" + +import datetime +import json +from pathlib import Path +from typing import List, TextIO + +from pydantic import BaseModel, RootModel + +from evm_transition_tool import FixtureFormats + +from ...common.base_types import HexNumber +from ..blockchain.types import Fixture as BlockchainFixture +from ..file.types import Fixtures +from ..state.types import Fixture as StateFixture + + +class TestCaseBase(BaseModel): + """ + Base model for a test case used in EEST consume commands. + """ + + id: str + fixture_hash: HexNumber | None + fork: str + format: FixtureFormats + __test__ = False # stop pytest from collecting this class as a test + + +class TestCaseStream(TestCaseBase): + """ + The test case model used to load test cases from a stream (stdin). + """ + + fixture: StateFixture | BlockchainFixture + __test__ = False # stop pytest from collecting this class as a test + + +class TestCaseIndexFile(TestCaseBase): + """ + The test case model used to save/load test cases to/from an index file. + """ + + json_path: Path + __test__ = False # stop pytest from collecting this class as a test + + # TODO: add pytest marks + """ + ConsumerTypes = Literal["all", "direct", "rlp", "engine"] + @classmethod + def _marks_default(cls): + return {consumer_type: [] for consumer_type in get_args(ConsumerTypes)} + marks: Mapping[ConsumerTypes, List[pytest.MarkDecorator]] = field( + default_factory=lambda: TestCase._marks_default() + ) + """ + + +class IndexFile(BaseModel): + """ + The model definition used for fixture index files. + """ + + root_hash: HexNumber | None + created_at: datetime.datetime + test_count: int + test_cases: List[TestCaseIndexFile] + + +class TestCases(RootModel): + """ + Root model defining a list test cases used in consume commands. + """ + + root: List[TestCaseIndexFile] | List[TestCaseStream] + __test__ = False # stop pytest from collecting this class as a test + + def __len__(self): + """Return the number of test cases in the root list.""" + return len(self.root) + + def __getitem__(self, position): + """Retrieve a test case by its index.""" + return self.root[position] + + def __setitem__(self, position, value): + """Set a test case at a particular index.""" + self.root[position] = value + + def __delitem__(self, position): + """Remove a test case at a particular index.""" + del self.root[position] + + def append(self, item): + """Append a test case to the root list.""" + self.root.append(item) + + def insert(self, position, value): + """Insert a test case at a given position.""" + self.root.insert(position, value) + + def remove(self, value): + """Remove a test case from the root list.""" + self.root.remove(value) + + def pop(self, position=-1): + """Remove and return a test case at the given position.""" + return self.root.pop(position) + + def clear(self): + """Remove all items from the root list.""" + self.root.clear() + + def __iter__(self): + """Return an iterator for the root list.""" + return iter(self.root) + + def __repr__(self): + """Return a string representation of the TestCases object.""" + return f"{self.__class__.__name__}(root={self.root})" + + @classmethod + def from_stream(cls, fd: TextIO) -> "TestCases": + """ + Create a TestCases object from a stream. + """ + fixtures = Fixtures.from_json_data(json.load(fd)) + test_cases = [] + for fixture_name, fixture in fixtures.items(): + if fixture.format == FixtureFormats.BLOCKCHAIN_TEST_HIVE: + print("Skipping hive fixture", fixture_name) + test_cases.append( + TestCaseStream( + id=fixture_name, + fixture_hash=fixture.hash, + fork=fixture.get_fork(), + format=fixture.format, + fixture=fixture, + ) + ) + return cls(root=test_cases) + + @classmethod + def from_index_file(cls, index_file: Path) -> "TestCases": + """ + Create a TestCases object from an index file. + """ + with open(index_file, "r") as fd: + index = IndexFile.model_validate_json(fd.read()) + return cls(root=index.test_cases) diff --git a/src/ethereum_test_tools/spec/eof/eof_test.py b/src/ethereum_test_tools/spec/eof/eof_test.py index 6349d30d32..8cb9b69eb5 100644 --- a/src/ethereum_test_tools/spec/eof/eof_test.py +++ b/src/ethereum_test_tools/spec/eof/eof_test.py @@ -2,15 +2,133 @@ Ethereum EOF test spec definition and filler. """ -from typing import Callable, ClassVar, Generator, List, Optional, Type +import subprocess +import warnings +from pathlib import Path +from shutil import which +from subprocess import CompletedProcess +from typing import Any, Callable, ClassVar, Generator, List, Optional, Type + +import pytest +from pydantic import Field, model_validator from ethereum_test_forks import Fork -from evm_transition_tool import FixtureFormats +from evm_transition_tool import FixtureFormats, TransitionTool +from ...common import Account, Address, Alloc, Environment, Transaction from ...common.base_types import Bytes -from ...exceptions import EOFException +from ...common.constants import TestAddress +from ...eof.v1 import Container +from ...exceptions import EOFException, EvmoneExceptionMapper from ..base.base_test import BaseFixture, BaseTest -from .types import Fixture +from ..state.state_test import StateTest +from .types import Fixture, Result + + +class EOFBaseException(Exception): + """ + Base exception class for exceptions raised when verifying EOF code. + """ + + def __init__(self, message): + super().__init__(message) + + @staticmethod + def format_code(code: Bytes, max_length=60) -> str: + """ + Avoid printing long bytecode strings in the terminal upon test failure. + """ + if len(code) > max_length: + half_length = max_length // 2 - 5 # Floor; adjust for ellipsis + return f"{code[:half_length].hex()}...{code[-half_length:].hex()}" + return code.hex() + + +class UnexpectedEOFException(EOFBaseException): + """ + Exception used when valid EOF code unexpectedly raises an exception in + eofparse. + """ + + def __init__(self, *, code: Bytes, got: str): + message = ( + "Expected EOF code to be valid, but an exception occurred:\n" + f" Code: {self.format_code(code)}\n" + f"Expected: No Exception\n" + f" Got: {got}" + ) + super().__init__(message) + + +class ExpectedEOFException(EOFBaseException): + """ + Exception used when EOF code is expected to raise an exception, but + eofparse did not raise an exception. + """ + + def __init__(self, *, code: Bytes, expected: str): + message = ( + "Expected EOF code to be invalid, but no exception was raised:\n" + f" Code: {self.format_code(code)}\n" + f"Expected: {expected}\n" + f" Got: No Exception" + ) + super().__init__(message) + + +class EOFExceptionMismatch(EOFBaseException): + """ + Exception used when the actual EOF exception differs from the expected one. + """ + + def __init__(self, code: Bytes, expected: str, got: str): + message = ( + "EOF code raised a different exception than expected:\n" + f" Code: {self.format_code(code)}\n" + f"Expected: {expected}\n" + f" Got: {got}" + ) + super().__init__(message) + + +class EOFParse: + """evmone-eofparse binary.""" + + binary: Path + + def __new__(cls): + """Make EOF binary a singleton.""" + if not hasattr(cls, "instance"): + cls.instance = super(EOFParse, cls).__new__(cls) + return cls.instance + + def __init__( + self, + binary: Optional[Path | str] = None, + ): + if binary is None: + which_path = which("evmone-eofparse") + if which_path is not None: + binary = Path(which_path) + if binary is None or not Path(binary).exists(): + raise FileNotFoundError( + "`evmone-eofparse` binary executable not found/not executable." + ) + self.binary = Path(binary) + + def run(self, *args: str, input: str | None = None) -> CompletedProcess: + """Run evmone with the given arguments""" + result = subprocess.run( + [self.binary, *args], + capture_output=True, + text=True, + input=input, + ) + if result.returncode not in [0, 1]: + raise Exception( + f"`{self.binary.name}` call failed with return code {result.returncode}." + ) + return result class EOFTest(BaseTest): @@ -22,10 +140,36 @@ class EOFTest(BaseTest): expect_exception: EOFException | None = None supported_fixture_formats: ClassVar[List[FixtureFormats]] = [ - # TODO: Potentially generate a state test and blockchain test too. FixtureFormats.EOF_TEST, ] + @model_validator(mode="before") + @classmethod + def check_container_exception(cls, data: Any) -> Any: + """ + Check if the container exception matches the expected exception. + """ + if isinstance(data, dict): + container = data.get("data") + expect_exception = data.get("expect_exception") + if container is not None and isinstance(container, Container): + if container.validity_error is not None: + if expect_exception is not None: + assert container.validity_error == expect_exception, ( + f"Container validity error {container.validity_error} " + f"does not match expected exception {expect_exception}." + ) + if expect_exception is None: + data["expect_exception"] = container.validity_error + return data + + @classmethod + def pytest_parameter_name(cls) -> str: + """ + Workaround for pytest parameter name. + """ + return "eof_test" + def make_eof_test_fixture( self, *, @@ -35,7 +179,7 @@ def make_eof_test_fixture( """ Generate the EOF test fixture. """ - return Fixture( + fixture = Fixture( vectors={ "0": { "code": self.data, @@ -48,10 +192,56 @@ def make_eof_test_fixture( } } ) + try: + eof_parse = EOFParse() + except FileNotFoundError as e: + warnings.warn(f"{e} Skipping EOF fixture verification. Fixtures may be invalid!") + return fixture + + for _, vector in fixture.vectors.items(): + expected_result = vector.results.get(fork.blockchain_test_network_name()) + if expected_result is None: + raise Exception(f"EOF Fixture missing vector result for fork: {fork}") + result = eof_parse.run(input=str(vector.code)) + self.verify_result(result, expected_result, vector.code) + + return fixture + + def verify_result(self, result: CompletedProcess, expected_result: Result, code: Bytes): + """ + Checks that the reported exception string matches the expected error. + """ + parser = EvmoneExceptionMapper() + actual_message = result.stdout.strip() + actual_exception = parser.message_to_exception(actual_message) + + if expected_result.exception is None: + if "OK" in actual_message: + return + else: + raise UnexpectedEOFException( + code=code, got=f"{actual_exception} ({actual_message})" + ) + + expected_exception = expected_result.exception + expected_message = parser.exception_to_message(expected_exception) + + if "OK" in actual_message: + raise ExpectedEOFException( + code=code, expected=f"{expected_exception} ({expected_message})" + ) + + if expected_exception != actual_exception: + raise EOFExceptionMismatch( + code=code, + expected=f"{expected_exception} ({expected_message})", + got=f"{actual_exception} ({actual_message})", + ) def generate( self, *, + t8n: TransitionTool, fork: Fork, eips: Optional[List[int]] = None, fixture_format: FixtureFormats, @@ -68,3 +258,84 @@ def generate( EOFTestSpec = Callable[[str], Generator[EOFTest, None, None]] EOFTestFiller = Type[EOFTest] + + +class EOFStateTest(EOFTest): + """ + Filler type that tests EOF containers and also generates a state/blockchain test. + """ + + tx_gas_limit: int = 10_000_000 + tx_data: Bytes = Bytes(b"") + env: Environment = Field(default_factory=Environment) + container_post: Account = Field(default_factory=Account) + + supported_fixture_formats: ClassVar[List[FixtureFormats]] = [ + FixtureFormats.EOF_TEST, + FixtureFormats.STATE_TEST, + FixtureFormats.BLOCKCHAIN_TEST, + FixtureFormats.BLOCKCHAIN_TEST_HIVE, + ] + + @classmethod + def pytest_parameter_name(cls) -> str: + """ + Workaround for pytest parameter name. + """ + return "eof_state_test" + + def generate_state_test(self) -> StateTest: + """ + Generate the StateTest filler. + """ + pre = Alloc() + container_address = Address(0x100) + pre[container_address] = Account(code=self.data, nonce=1) + pre[TestAddress] = Account(balance=1_000_000_000_000_000_000_000, nonce=0) + tx = Transaction( + nonce=0, + to=container_address, + gas_limit=self.tx_gas_limit, + gas_price=10, + protected=False, + data=self.tx_data, + ) + post = Alloc() + post[container_address] = self.container_post + return StateTest( + pre=pre, + tx=tx, + env=self.env, + post=post, + ) + + def generate( + self, + *, + t8n: TransitionTool, + fork: Fork, + eips: Optional[List[int]] = None, + fixture_format: FixtureFormats, + **_, + ) -> BaseFixture: + """ + Generate the BlockchainTest fixture. + """ + if fixture_format == FixtureFormats.EOF_TEST: + return self.make_eof_test_fixture(fork=fork, eips=eips) + elif fixture_format in ( + FixtureFormats.STATE_TEST, + FixtureFormats.BLOCKCHAIN_TEST, + FixtureFormats.BLOCKCHAIN_TEST_HIVE, + ): + if self.expect_exception is not None: + pytest.skip("State tests can't be generated for invalid EOF code yet.") + return self.generate_state_test().generate( + t8n=t8n, fork=fork, fixture_format=fixture_format, eips=eips + ) + + raise Exception(f"Unknown fixture format: {fixture_format}") + + +EOFStateTestSpec = Callable[[str], Generator[EOFStateTest, None, None]] +EOFStateTestFiller = Type[EOFStateTest] diff --git a/src/ethereum_test_tools/spec/file/types.py b/src/ethereum_test_tools/spec/file/types.py index 3b3622211c..608d94ab09 100644 --- a/src/ethereum_test_tools/spec/file/types.py +++ b/src/ethereum_test_tools/spec/file/types.py @@ -109,7 +109,7 @@ def from_json_data( FixtureFormats.STATE_TEST.value: StateFixtures, } - if fixture_format is not None: + if fixture_format not in [None, "unset_test_format", FixtureFormats.UNSET_TEST_FORMAT]: if fixture_format not in model_mapping: raise TypeError(f"Unsupported fixture format: {fixture_format}") model_class = model_mapping[fixture_format] diff --git a/src/ethereum_test_tools/spec/fixture_collector.py b/src/ethereum_test_tools/spec/fixture_collector.py index f8df0c692a..4b710d6912 100644 --- a/src/ethereum_test_tools/spec/fixture_collector.py +++ b/src/ethereum_test_tools/spec/fixture_collector.py @@ -3,14 +3,17 @@ fixtures. """ +import json import os import re +import sys from dataclasses import dataclass, field from pathlib import Path from typing import Dict, Literal, Optional, Tuple from evm_transition_tool import FixtureFormats, TransitionTool +from ..common.json import to_json from .base.base_test import BaseFixture from .file.types import Fixtures @@ -127,7 +130,7 @@ def get_fixture_basename(self, info: TestInfo) -> Path: return module_relative_output_dir / strip_test_prefix(info.get_single_test_name()) return module_relative_output_dir / strip_test_prefix(info.original_name) - def add_fixture(self, info: TestInfo, fixture: BaseFixture) -> None: + def add_fixture(self, info: TestInfo, fixture: BaseFixture) -> Path: """ Adds a fixture to the list of fixtures of a given test case. """ @@ -144,10 +147,18 @@ def add_fixture(self, info: TestInfo, fixture: BaseFixture) -> None: self.all_fixtures[fixture_path][info.id] = fixture + return fixture_path + def dump_fixtures(self) -> None: """ Dumps all collected fixtures to their respective files. """ + if self.output_dir == "stdout": + combined_fixtures = { + k: to_json(v) for fixture in self.all_fixtures.values() for k, v in fixture.items() + } + json.dump(combined_fixtures, sys.stdout, indent=4) + return os.makedirs(self.output_dir, exist_ok=True) for fixture_path, fixtures in self.all_fixtures.items(): os.makedirs(fixture_path.parent, exist_ok=True) @@ -165,7 +176,10 @@ def verify_fixture_files(self, evm_fixture_verification: TransitionTool) -> None info = self.json_path_to_test_item[fixture_path] verify_fixtures_dump_dir = self._get_verify_fixtures_dump_dir(info) evm_fixture_verification.verify_fixture( - fixture.format, fixture_path, verify_fixtures_dump_dir + fixture.format, + fixture_path, + fixture_name=None, + debug_output_path=verify_fixtures_dump_dir, ) def _get_verify_fixtures_dump_dir( diff --git a/src/ethereum_test_tools/spec/state/types.py b/src/ethereum_test_tools/spec/state/types.py index 71205546aa..bd4de25974 100644 --- a/src/ethereum_test_tools/spec/state/types.py +++ b/src/ethereum_test_tools/spec/state/types.py @@ -96,3 +96,11 @@ class Fixture(BaseFixture): post: Mapping[str, List[FixtureForkPost]] format: ClassVar[FixtureFormats] = FixtureFormats.STATE_TEST + + def get_fork(self) -> str: + """ + Returns the fork of the fixture as a string. + """ + forks = list(self.post.keys()) + assert len(forks) == 1, "Expected state test fixture with single fork" + return forks[0] diff --git a/src/ethereum_test_tools/tests/test_types.py b/src/ethereum_test_tools/tests/test_types.py index 76ec5835a4..fe46c1fd7d 100644 --- a/src/ethereum_test_tools/tests/test_types.py +++ b/src/ethereum_test_tools/tests/test_types.py @@ -5,6 +5,7 @@ from typing import Any, Dict, List import pytest +from pydantic import TypeAdapter from ..common import ( AccessList, @@ -18,7 +19,7 @@ from ..common.base_types import Address, Bloom, Bytes, Hash, HeaderNonce, ZeroPaddedHexNumber from ..common.constants import TestPrivateKey from ..common.json import to_json -from ..common.types import Alloc +from ..common.types import Alloc, DepositRequest, Requests from ..exceptions import BlockException, TransactionException from ..spec.blockchain.types import ( FixtureBlockBase, @@ -1130,6 +1131,7 @@ def test_account_merge( ).with_signature_and_sender(), ], withdrawals=[Withdrawal(index=0, validator_index=1, address=0x1234, amount=2)], + requests=None, ), { "parentHash": Hash(0).hex(), @@ -1222,6 +1224,7 @@ def test_account_merge( amount=2, ) ], + requests=None, ), validation_error=TransactionException.INTRINSIC_GAS_TOO_LOW, version=1, @@ -1322,6 +1325,7 @@ def test_account_merge( ).with_signature_and_sender(), ], withdrawals=[Withdrawal(index=0, validator_index=1, address=0x1234, amount=2)], + requests=None, ), version=1, validation_error=[ @@ -1658,3 +1662,42 @@ def test_withdrawals_root(withdrawals: List[Withdrawal], expected_root: bytes): Test that withdrawals_root returns the expected hash. """ assert Withdrawal.list_root(withdrawals) == expected_root + + +@pytest.mark.parametrize( + ["json_str", "type_adapter", "expected"], + [ + pytest.param( + """ + [ + { + "type": "0x0", + "pubkey": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001", + "withdrawalCredentials": "0x0000000000000000000000000000000000000000000000000000000000000002", + "amount": "0x1234", + "signature": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003", + "index": "0x5678" + } + ] + """, # noqa: E501 + TypeAdapter(Requests), + Requests( + root=[ + DepositRequest( + pubkey=1, + withdrawal_credentials=2, + amount=0x1234, + signature=3, + index=0x5678, + ), + ] + ), + id="requests_1", + ), + ], +) +def test_parsing(json_str: str, type_adapter: TypeAdapter, expected: Any): + """ + Test that parsing the given JSON string returns the expected object. + """ + assert type_adapter.validate_json(json_str) == expected diff --git a/src/ethereum_test_tools/tests/test_vm.py b/src/ethereum_test_tools/tests/test_vm.py index 5bfd7ecb9b..01d6628506 100644 --- a/src/ethereum_test_tools/tests/test_vm.py +++ b/src/ethereum_test_tools/tests/test_vm.py @@ -179,7 +179,7 @@ [ Op.ORIGIN.int(), Op.RJUMPV.int(), - 0x03, # Data portion, defined by the [1, 2, 3] argument + 0x02, # Data portion, defined by the [1, 2, 3] argument 0x00, 0x01, 0x00, @@ -203,7 +203,7 @@ bytes( [ Op.RJUMPV.int(), - 0x03, + 0x02, 0xFF, 0xFF, 0xFF, @@ -218,7 +218,7 @@ bytes( [ Op.RJUMPV.int(), - 0x05, + 0x04, 0x00, 0x00, 0x00, @@ -238,7 +238,7 @@ [ Op.ORIGIN.int(), Op.RJUMPV.int(), - 0x03, # Data portion, defined by the [1, 2, 3] argument + 0x02, # Data portion, defined by the [1, 2, 3] argument 0x00, 0x01, 0x00, @@ -258,6 +258,13 @@ ] ), ), + (Op.RJUMPV[0, 3, 6, 9], bytes.fromhex("e2030000000300060009")), + (Op.RJUMPV[2, 0], bytes.fromhex("e20100020000")), + (Op.RJUMPV[b"\x02\x00\x02\xFF\xFF"], bytes.fromhex("e2020002ffff")), + (Op.EXCHANGE[0x2 + 0x0, 0x3 + 0x0], bytes.fromhex("e800")), + (Op.EXCHANGE[0x2 + 0x0, 0x3 + 0xF], bytes.fromhex("e80f")), + (Op.EXCHANGE[0x2 + 0xF, 0x3 + 0xF + 0x0], bytes.fromhex("e8f0")), + (Op.EXCHANGE[0x2 + 0xF, 0x3 + 0xF + 0xF], bytes.fromhex("e8ff")), ], ) def test_opcodes(opcodes: bytes, expected: bytes): diff --git a/src/ethereum_test_tools/vm/__init__.py b/src/ethereum_test_tools/vm/__init__.py index c1042ef82b..510d0d47b1 100644 --- a/src/ethereum_test_tools/vm/__init__.py +++ b/src/ethereum_test_tools/vm/__init__.py @@ -2,11 +2,12 @@ Ethereum Virtual Machine related definitions and utilities. """ -from .opcode import Macro, Opcode, OpcodeCallArg, Opcodes +from .opcode import Macro, Macros, Opcode, OpcodeCallArg, Opcodes __all__ = ( "Opcode", "Macro", + "Macros", "OpcodeCallArg", "Opcodes", ) diff --git a/src/ethereum_test_tools/vm/opcode.py b/src/ethereum_test_tools/vm/opcode.py index 8b503670c3..bb3209babf 100644 --- a/src/ethereum_test_tools/vm/opcode.py +++ b/src/ethereum_test_tools/vm/opcode.py @@ -172,7 +172,10 @@ def __getitem__( data_portion = bytes() if self.data_portion_formatter is not None: - data_portion = self.data_portion_formatter(*args) + if len(args) == 1 and isinstance(args[0], Iterable) and not isinstance(args[0], bytes): + data_portion = self.data_portion_formatter(*args[0]) + else: + data_portion = self.data_portion_formatter(*args) elif self.data_portion_length > 0: # For opcodes with a data portion, the first argument is the data and the rest of the # arguments form the stack. @@ -253,8 +256,10 @@ def __call__( raise ValueError("Opcode with data portion requires at least one argument") if self.data_portion_formatter is not None: data_portion_arg = args.pop(0) - assert isinstance(data_portion_arg, Iterable) - data_portion = self.data_portion_formatter(*data_portion_arg) + if isinstance(data_portion_arg, Iterable) and not isinstance(data_portion_arg, bytes): + data_portion = self.data_portion_formatter(*data_portion_arg) + else: + data_portion = self.data_portion_formatter(data_portion_arg) elif self.data_portion_length > 0: # For opcodes with a data portion, the first argument is the data and the rest of the # arguments form the stack. @@ -382,14 +387,14 @@ def _rjumpv_encoder(*args: int | bytes | Iterable[int]) -> bytes: elif isinstance(args[0], Iterable): int_args = list(args[0]) return b"".join( - [len(int_args).to_bytes(RJUMPV_MAX_INDEX_BYTE_LENGTH, "big")] + [(len(int_args) - 1).to_bytes(RJUMPV_MAX_INDEX_BYTE_LENGTH, "big")] + [ i.to_bytes(RJUMPV_BRANCH_OFFSET_BYTE_LENGTH, "big", signed=True) for i in int_args ] ) return b"".join( - [len(args).to_bytes(RJUMPV_MAX_INDEX_BYTE_LENGTH, "big")] + [(len(args) - 1).to_bytes(RJUMPV_MAX_INDEX_BYTE_LENGTH, "big")] + [ i.to_bytes(RJUMPV_BRANCH_OFFSET_BYTE_LENGTH, "big", signed=True) for i in args @@ -398,6 +403,27 @@ def _rjumpv_encoder(*args: int | bytes | Iterable[int]) -> bytes: ) +def _exchange_encoder(*args: int) -> bytes: + assert 1 <= len(args) <= 2, f"Exchange opcode requires one or two arguments, got {len(args)}" + if len(args) == 1: + return int.to_bytes(args[0], 1, "big") + # n = imm >> 4 + 1 + # m = imm & 0xF + 1 + # x = n + 1 + # y = n + m + 1 + # ... + # n = x - 1 + # m = y - x + # m = y - n - 1 + x, y = args + assert 2 <= x <= 0x11 + assert x + 1 <= y <= x + 0x10 + n = x - 1 + m = y - x + imm = (n - 1) << 4 | m - 1 + return int.to_bytes(imm, 1, "big") + + class Opcodes(Opcode, Enum): """ Enum containing all known opcodes. @@ -4932,7 +4958,7 @@ class Opcodes(Opcode, Enum): 3 """ - JUMPF = Opcode(0xB1, data_portion_length=2) + JUMPF = Opcode(0xE5, data_portion_length=2) """ !!! Note: This opcode is under development @@ -4976,6 +5002,13 @@ class Opcodes(Opcode, Enum): Description ---- + - deduct 3 gas + - read uint8 operand imm + - n = imm + 1 + - nโ€˜th (1-based) stack item is duplicated at the top of the stack + - Stack validation: stack_height >= n + + Inputs ---- @@ -4984,6 +5017,7 @@ class Opcodes(Opcode, Enum): Fork ---- + EOF Fork Gas ---- @@ -5000,6 +5034,13 @@ class Opcodes(Opcode, Enum): Description ---- + - deduct 3 gas + - read uint8 operand imm + - n = imm + 1 + - n + 1th stack item is swapped with the top stack item (1-based). + - Stack validation: stack_height >= n + 1 + + Inputs ---- @@ -5008,17 +5049,53 @@ class Opcodes(Opcode, Enum): Fork ---- + EOF Fork Gas ---- """ - CREATE3 = Opcode(0xEC, popped_stack_items=4, pushed_stack_items=1, data_portion_length=1) + EXCHANGE = Opcode(0xE8, data_portion_formatter=_exchange_encoder) + """ + !!! Note: This opcode is under development + + EXCHANGE[x, y] + ---- + + Description + ---- + Exchanges two stack positions. Two nybbles, n is high 4 bits + 1, then m is 4 low bits + 1. + Exchanges tne n+1'th item with the n + m + 1 item. + + Inputs x and y when the opcode is used as `EXCHANGE[x, y]`, are equal to: + - x = n + 1 + - y = n + m + 1 + Which each equals to 1-based stack positions swapped. + + Inputs + ---- + n + m + 1, or ((imm >> 4) + (imm &0x0F) + 3) from the raw immediate, + + Outputs + ---- + n + m + 1, or ((imm >> 4) + (imm &0x0F) + 3) from the raw immediate, + + Fork + ---- + EOF_FORK + + Gas + ---- + 3 + + """ + + EOFCREATE = Opcode(0xEC, popped_stack_items=4, pushed_stack_items=1, data_portion_length=1) """ !!! Note: This opcode is under development - CREATE3() + EOFCREATE[initcontainer_index](value, salt, input_offset, input_size) ---- Description diff --git a/src/evm_transition_tool/besu.py b/src/evm_transition_tool/besu.py index daa93ae481..5c38f76d01 100644 --- a/src/evm_transition_tool/besu.py +++ b/src/evm_transition_tool/besu.py @@ -3,8 +3,10 @@ """ import json +import os import re import subprocess +import tempfile import textwrap from pathlib import Path from re import compile @@ -30,6 +32,7 @@ class BesuTransitionTool(TransitionTool): trace: bool process: Optional[subprocess.Popen] = None server_url: str + besu_trace_dir: Optional[tempfile.TemporaryDirectory] def __init__( self, @@ -46,17 +49,24 @@ def __init__( except Exception as e: raise Exception(f"Unexpected exception calling evm tool: {e}.") self.help_string = result.stdout + self.besu_trace_dir = tempfile.TemporaryDirectory() if self.trace else None def start_server(self): """ Starts the t8n-server process, extracts the port, and leaves it running for future re-use. """ + args = [ + str(self.binary), + "t8n-server", + "--port=0", # OS assigned server port + ] + + if self.trace: + args.append("--trace") + args.append(f"--output.basedir={self.besu_trace_dir.name}") + self.process = subprocess.Popen( - args=[ - str(self.binary), - "t8n-server", - "--port=0", # OS assigned server port - ], + args=args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, ) @@ -67,7 +77,7 @@ def start_server(self): if not line or "Failed to start transition server" in line: raise Exception("Failed starting Besu subprocess\n" + line) if "Transition server listening on" in line: - port = re.search("Transition server listening on ([0-9]+)", line).group(1) + port = re.search("Transition server listening on (\\d+)", line).group(1) self.server_url = f"http://localhost:{port}/" break @@ -77,6 +87,8 @@ def shutdown(self): """ if self.process: self.process.kill() + if self.besu_trace_dir: + self.besu_trace_dir.cleanup() def evaluate( self, @@ -98,9 +110,6 @@ def evaluate( if eips is not None: fork_name = "+".join([fork_name] + [str(eip) for eip in eips]) - if self.trace: - raise Exception("Besu `t8n-server` does not support tracing.") - input_json = { "alloc": alloc, "txs": txs, @@ -175,6 +184,14 @@ def evaluate( }, ) + if self.trace and self.besu_trace_dir: + self.collect_traces( + output["result"]["receipts"], self.besu_trace_dir, debug_output_path + ) + for i, r in enumerate(output["result"]["receipts"]): + trace_file_name = f"trace-{i}-{r['transactionHash']}.jsonl" + os.remove(os.path.join(self.besu_trace_dir.name, trace_file_name)) + return output def is_fork_supported(self, fork: Fork) -> bool: diff --git a/src/evm_transition_tool/execution_specs.py b/src/evm_transition_tool/execution_specs.py index b7d06f430f..ccffb92f76 100644 --- a/src/evm_transition_tool/execution_specs.py +++ b/src/evm_transition_tool/execution_specs.py @@ -11,6 +11,7 @@ from ethereum_test_forks import Constantinople, ConstantinopleFix, Fork from .geth import GethTransitionTool +from .transition_tool import FixtureFormats UNSUPPORTED_FORKS = ( Constantinople, @@ -99,3 +100,29 @@ def is_fork_supported(self, fork: Fork) -> bool: Currently, ethereum-spec-evm provides no way to determine supported forks. """ return fork not in UNSUPPORTED_FORKS + + def get_blocktest_help(self) -> str: + """ + Return the help string for the blocktest subcommand. + """ + raise NotImplementedError( + "The `blocktest` command is not supported by the ethereum-spec-evm. " + "Use geth's evm tool." + ) + + def verify_fixture( + self, + fixture_format: FixtureFormats, + fixture_path: Path, + fixture_name: Optional[str] = None, + debug_output_path: Optional[Path] = None, + ): + """ + Executes `evm [state|block]test` to verify the fixture at `fixture_path`. + + Currently only implemented by geth's evm. + """ + raise NotImplementedError( + "The `verify_fixture()` function is not supported by the ethereum-spec-evm. " + "Use geth's evm tool." + ) diff --git a/src/evm_transition_tool/geth.py b/src/evm_transition_tool/geth.py index 0ad7efd32f..b4e8ef5ad7 100644 --- a/src/evm_transition_tool/geth.py +++ b/src/evm_transition_tool/geth.py @@ -10,8 +10,6 @@ from re import compile from typing import Optional -import pytest - from ethereum_test_forks import Fork from .transition_tool import FixtureFormats, TransitionTool, dump_files_to_directory @@ -56,19 +54,25 @@ def is_fork_supported(self, fork: Fork) -> bool: """ return fork.transition_tool_name() in self.help_string - def process_statetest_result(self, result: str): + def get_blocktest_help(self) -> str: """ - Process the result of a `evm statetest` to parse as JSON and raise if any test failed. + Return the help string for the blocktest subcommand. """ - result_json = json.loads(result) - if not isinstance(result_json, list): - raise Exception(f"Unexpected result from evm statetest: {result_json}") - for test_result in result_json: - if not test_result["pass"]: - pytest.fail(f"Test failed: {test_result['name']}. Error: {test_result['error']}") + args = [str(self.binary), "blocktest", "--help"] + try: + result = subprocess.run(args, capture_output=True, text=True) + except subprocess.CalledProcessError as e: + raise Exception("evm process unexpectedly returned a non-zero status code: " f"{e}.") + except Exception as e: + raise Exception(f"Unexpected exception calling evm tool: {e}.") + return result.stdout def verify_fixture( - self, fixture_format: FixtureFormats, fixture_path: Path, debug_output_path: Optional[Path] + self, + fixture_format: FixtureFormats, + fixture_path: Path, + fixture_name: Optional[str] = None, + debug_output_path: Optional[Path] = None, ): """ Executes `evm [state|block]test` to verify the fixture at `fixture_path`. @@ -87,6 +91,10 @@ def verify_fixture( else: raise Exception(f"Invalid test fixture format: {fixture_format}") + if fixture_name and fixture_format == FixtureFormats.BLOCKCHAIN_TEST: + assert isinstance(fixture_name, str), "fixture_name must be a string" + command.append("--run") + command.append(fixture_name) command.append(str(fixture_path)) result = subprocess.run( @@ -95,12 +103,8 @@ def verify_fixture( stderr=subprocess.PIPE, ) - if FixtureFormats.is_state_test(fixture_format): - self.process_statetest_result(result.stdout.decode()) - if debug_output_path: debug_fixture_path = debug_output_path / "fixtures.json" - shutil.copyfile(fixture_path, debug_fixture_path) # Use the local copy of the fixture in the debug directory verify_fixtures_call = " ".join(command[:-1]) + f" {debug_fixture_path}" verify_fixtures_script = textwrap.dedent( @@ -119,9 +123,17 @@ def verify_fixture( "verify_fixtures.sh+x": verify_fixtures_script, }, ) + shutil.copyfile(fixture_path, debug_fixture_path) if result.returncode != 0: raise Exception( - f"Failed to verify fixture via: '{' '.join(command)}'. " - f"Error: '{result.stderr.decode()}'" + f"EVM test failed.\n{' '.join(command)}\n\n Error:\n{result.stderr.decode()}" ) + + if FixtureFormats.is_state_test(fixture_format): + result_json = json.loads(result.stdout.decode()) + if not isinstance(result_json, list): + raise Exception(f"Unexpected result from evm statetest: {result_json}") + else: + result_json = [] # there is no parseable format for blocktest output + return result_json diff --git a/src/evm_transition_tool/transition_tool.py b/src/evm_transition_tool/transition_tool.py index 5173a55109..9b718106f8 100644 --- a/src/evm_transition_tool/transition_tool.py +++ b/src/evm_transition_tool/transition_tool.py @@ -583,13 +583,17 @@ def evaluate( ) def verify_fixture( - self, fixture_format: FixtureFormats, fixture_path: Path, debug_output_path: Optional[Path] + self, + fixture_format: FixtureFormats, + fixture_path: Path, + fixture_name: Optional[str] = None, + debug_output_path: Optional[Path] = None, ): """ Executes `evm [state|block]test` to verify the fixture at `fixture_path`. Currently only implemented by geth's evm. """ - raise Exception( + raise NotImplementedError( "The `verify_fixture()` function is not supported by this tool. Use geth's evm tool." ) diff --git a/src/pytest_plugins/consume/__init__.py b/src/pytest_plugins/consume/__init__.py new file mode 100644 index 0000000000..18fbc2c630 --- /dev/null +++ b/src/pytest_plugins/consume/__init__.py @@ -0,0 +1,3 @@ +""" +Pytest plugins for consume commands. +""" diff --git a/src/pytest_plugins/consume/consume.py b/src/pytest_plugins/consume/consume.py new file mode 100644 index 0000000000..c53615a48d --- /dev/null +++ b/src/pytest_plugins/consume/consume.py @@ -0,0 +1,221 @@ +""" +A pytest plugin providing common functionality for consuming test fixtures. +""" + +import os +import sys +import tarfile +from pathlib import Path +from typing import Literal, Union +from urllib.parse import urlparse + +import pytest +import requests +import rich + +from cli.gen_index import generate_fixtures_index +from ethereum_test_tools.spec.consume.types import TestCases +from evm_transition_tool import FixtureFormats + +cached_downloads_directory = Path("./cached_downloads") + +JsonSource = Union[Path, Literal["stdin"]] + + +def default_input_directory() -> str: + """ + The default directory to consume generated test fixtures from. Defined as a + function to allow for easier testing. + """ + return "./fixtures" + + +def default_html_report_filename() -> str: + """ + The default file to store the generated HTML test report. Defined as a + function to allow for easier testing. + """ + return "report_consume.html" + + +def is_url(string: str) -> bool: + """ + Check if a string is a remote URL. + """ + result = urlparse(string) + return all([result.scheme, result.netloc]) + + +def download_and_extract(url: str, base_directory: Path) -> Path: + """ + Download the URL and extract it locally if it hasn't already been downloaded. + """ + parsed_url = urlparse(url) + filename = Path(parsed_url.path).name + version = Path(parsed_url.path).parts[-2] + extract_to = base_directory / version / filename.removesuffix(".tar.gz") + + if extract_to.exists(): + # skip download if the archive has already been downloaded + return extract_to + + extract_to.mkdir(parents=True, exist_ok=False) + response = requests.get(url) + response.raise_for_status() + + archive_path = extract_to / filename + with open(archive_path, "wb") as file: + file.write(response.content) + + with tarfile.open(archive_path, "r:gz") as tar: + tar.extractall(path=extract_to) + + return extract_to + + +def pytest_addoption(parser): # noqa: D103 + consume_group = parser.getgroup( + "consume", "Arguments related to consuming fixtures via a client" + ) + consume_group.addoption( + "--input", + action="store", + dest="fixture_source", + default=default_input_directory(), + help=( + "A URL or local directory specifying the JSON test fixtures. Default: " + f"'{default_input_directory()}'." + ), + ) + consume_group.addoption( + "--no-html", + action="store_true", + dest="disable_html", + default=False, + help=( + "Don't generate an HTML test report (in the output directory). " + "The --html flag can be used to specify a different path." + ), + ) + + +@pytest.hookimpl(tryfirst=True) +def pytest_configure(config): # noqa: D103 + """ + Pytest hook called after command line options have been parsed and before + test collection begins. + + `@pytest.hookimpl(tryfirst=True)` is applied to ensure that this hook is + called before the pytest-html plugin's pytest_configure to ensure that + it uses the modified `htmlpath` option. + """ + input_source = config.getoption("fixture_source") + if input_source == "stdin": + config.test_cases = TestCases.from_stream(sys.stdin) + return + + if is_url(input_source): + cached_downloads_directory.mkdir(parents=True, exist_ok=True) + input_source = download_and_extract(input_source, cached_downloads_directory) + config.option.fixture_source = input_source + + input_source = Path(input_source) + if not input_source.exists(): + pytest.exit(f"Specified fixture directory '{input_source}' does not exist.") + if not any(input_source.glob("**/*.json")): + pytest.exit( + f"Specified fixture directory '{input_source}' does not contain any JSON files." + ) + + index_file = input_source / "index.json" + if not index_file.exists(): + rich.print(f"Generating index file [bold cyan]{index_file}[/]...") + generate_fixtures_index( + Path(input_source), quiet_mode=False, force_flag=False, disable_infer_format=False + ) + config.test_cases = TestCases.from_index_file(Path(input_source) / "index.json") + + if config.option.collectonly: + return + if not config.getoption("disable_html") and config.getoption("htmlpath") is None: + # generate an html report by default, unless explicitly disabled + config.option.htmlpath = os.path.join( + config.getoption("fixture_source"), default_html_report_filename() + ) + + +def pytest_html_report_title(report): + """ + Set the HTML report title (pytest-html plugin). + """ + report.title = "Consume Test Report" + + +def pytest_report_header(config): # noqa: D103 + input_source = config.getoption("fixture_source") + return f"fixtures: {input_source}" + + +@pytest.fixture(scope="function") +def fixture_source(request) -> JsonSource: # noqa: D103 + return request.config.getoption("fixture_source") + + +def pytest_generate_tests(metafunc): + """ + Generate test cases for every test fixture in all the JSON fixture files + within the specified fixtures directory, or read from stdin if the directory is 'stdin'. + """ + test_cases = metafunc.config.test_cases + + if "test_blocktest" in metafunc.function.__name__: + pytest_params = [ + pytest.param( + test_case, + id=test_case.id, + # marks=test_case.marks["all"] + test_case.marks["direct"], + ) + for test_case in test_cases + if test_case.format == FixtureFormats.BLOCKCHAIN_TEST + ] + metafunc.parametrize("test_case", pytest_params) + + if "test_statetest" in metafunc.function.__name__: + pytest_params = [ + pytest.param( + test_case, + id=test_case.id, + # marks=test_case.marks["all"] + test_case.marks["direct"], + ) + for test_case in test_cases + if test_case.format == FixtureFormats.STATE_TEST + ] + metafunc.parametrize("test_case", pytest_params) + + if "test_via_rlp" in metafunc.function.__name__: + pytest_params = [ + pytest.param( + test_case, + id=test_case.id, + # marks=test_case.marks["all"] + test_case.marks["rlp"], + ) + for test_case in test_cases + if test_case.format == FixtureFormats.BLOCKCHAIN_TEST + ] + metafunc.parametrize("test_case", pytest_params) + + if "test_via_engine" in metafunc.function.__name__: + pytest_params = [ + pytest.param( + test_case, + id=test_case.id, + # marks=test_case.marks["all"] + test_case.marks["engine"], + ) + for test_case in test_cases + if test_case.format == FixtureFormats.BLOCKCHAIN_TEST_HIVE + ] + metafunc.parametrize("test_case", pytest_params) + + if "client_type" in metafunc.fixturenames: + client_ids = [client.name for client in metafunc.config.hive_execution_clients] + metafunc.parametrize("client_type", metafunc.config.hive_execution_clients, ids=client_ids) diff --git a/src/pytest_plugins/consume/direct.py b/src/pytest_plugins/consume/direct.py new file mode 100644 index 0000000000..45f7e5305a --- /dev/null +++ b/src/pytest_plugins/consume/direct.py @@ -0,0 +1,130 @@ +""" +A pytest plugin that configures the consume command to act as a test runner +for "direct" client fixture consumer interfaces. + +For example, via go-ethereum's `evm blocktest` or `evm statetest` commands. +""" + +import json +import tempfile +from pathlib import Path +from typing import Generator, Optional + +import pytest + +from ethereum_test_tools.common.json import to_json +from ethereum_test_tools.spec.consume.types import TestCaseIndexFile, TestCaseStream +from ethereum_test_tools.spec.file.types import Fixtures +from evm_transition_tool import TransitionTool + + +def pytest_addoption(parser): # noqa: D103 + consume_group = parser.getgroup( + "consume_direct", "Arguments related to consuming fixtures via a client" + ) + + consume_group.addoption( + "--evm-bin", + action="store", + dest="evm_bin", + type=Path, + default=None, + help=( + "Path to an evm executable that provides `blocktest`. Default: First 'evm' entry in " + "PATH." + ), + ) + consume_group.addoption( + "--traces", + action="store_true", + dest="evm_collect_traces", + default=False, + help="Collect traces of the execution information from the transition tool.", + ) + debug_group = parser.getgroup("debug", "Arguments defining debug behavior") + debug_group.addoption( + "--evm-dump-dir", + action="store", + dest="base_dump_dir", + type=Path, + default=None, + help="Path to dump the transition tool debug output.", + ) + + +def pytest_configure(config): # noqa: D103 + evm = TransitionTool.from_binary_path( + binary_path=config.getoption("evm_bin"), + # TODO: The verify_fixture() method doesn't currently use this option. + trace=config.getoption("evm_collect_traces"), + ) + try: + blocktest_help_string = evm.get_blocktest_help() + except NotImplementedError as e: + pytest.exit(str(e)) + config.evm = evm + config.evm_run_single_test = "--run" in blocktest_help_string + + +@pytest.fixture(autouse=True, scope="session") +def evm(request) -> Generator[TransitionTool, None, None]: + """ + Returns the interface to the evm binary that will consume tests. + """ + yield request.config.evm + request.config.evm.shutdown() + + +@pytest.fixture(scope="session") +def evm_run_single_test(request) -> bool: + """ + Helper specifying whether to execute one test per fixture in each json file. + """ + return request.config.evm_run_single_test + + +@pytest.fixture(scope="function") +def test_dump_dir( + request, fixture_path: Path, fixture_name: str, evm_run_single_test: bool +) -> Optional[Path]: + """ + The directory to write evm debug output to. + """ + base_dump_dir = request.config.getoption("base_dump_dir") + if not base_dump_dir: + return None + if evm_run_single_test: + if len(fixture_name) > 142: + # ensure file name is not too long for eCryptFS + fixture_name = fixture_name[:70] + "..." + fixture_name[-70:] + return base_dump_dir / fixture_path.stem / fixture_name.replace("/", "-") + return base_dump_dir / fixture_path.stem + + +@pytest.fixture +def fixture_path(test_case: TestCaseIndexFile | TestCaseStream, fixture_source): + """ + The path to the current JSON fixture file. + + If the fixture source is stdin, the fixture is written to a temporary json file. + """ + if fixture_source == "stdin": + assert isinstance(test_case, TestCaseStream) + temp_dir = tempfile.TemporaryDirectory() + fixture_path = Path(temp_dir.name) / f"{test_case.id.replace('/','_')}.json" + fixtures = Fixtures({test_case.id: test_case.fixture}) + with open(fixture_path, "w") as f: + json.dump(to_json(fixtures), f, indent=4) + yield fixture_path + temp_dir.cleanup() + else: + assert isinstance(test_case, TestCaseIndexFile) + yield fixture_source / test_case.json_path + + +@pytest.fixture(scope="function") +def fixture_name(test_case: TestCaseIndexFile | TestCaseStream): + """ + The name of the current fixture. + """ + return test_case.id diff --git a/src/pytest_plugins/consume/engine.py b/src/pytest_plugins/consume/engine.py new file mode 100644 index 0000000000..bb85d035e1 --- /dev/null +++ b/src/pytest_plugins/consume/engine.py @@ -0,0 +1,24 @@ +""" +A hive simulator that executes blocks against clients using the `engine_newPayloadVX` method from +the Engine API, verifying the appropriate VALID/INVALID responses. + +Implemented using the pytest framework as a pytest plugin. +""" + +import pytest + + +@pytest.fixture(scope="session") +def test_suite_name() -> str: + """ + The name of the hive test suite used in this simulator. + """ + return "EEST Consume Blocks via Engine API" + + +@pytest.fixture(scope="session") +def test_suite_description() -> str: + """ + The description of the hive test suite used in this simulator. + """ + return "Execute blockchain tests by against clients using the `engine_newPayloadVX` method." diff --git a/src/pytest_plugins/consume/hive_ruleset.py b/src/pytest_plugins/consume/hive_ruleset.py new file mode 100644 index 0000000000..bdde7679f9 --- /dev/null +++ b/src/pytest_plugins/consume/hive_ruleset.py @@ -0,0 +1,341 @@ +""" +Network/fork rules for Hive, taken verbatim from the consensus simulator. +""" + +ruleset = { + "Frontier": { + "HIVE_FORK_HOMESTEAD": 2000, + "HIVE_FORK_DAO_BLOCK": 2000, + "HIVE_FORK_TANGERINE": 2000, + "HIVE_FORK_SPURIOUS": 2000, + "HIVE_FORK_BYZANTIUM": 2000, + "HIVE_FORK_CONSTANTINOPLE": 2000, + "HIVE_FORK_PETERSBURG": 2000, + "HIVE_FORK_ISTANBUL": 2000, + "HIVE_FORK_BERLIN": 2000, + "HIVE_FORK_LONDON": 2000, + }, + "Homestead": { + "HIVE_FORK_HOMESTEAD": 0, + "HIVE_FORK_DAO_BLOCK": 2000, + "HIVE_FORK_TANGERINE": 2000, + "HIVE_FORK_SPURIOUS": 2000, + "HIVE_FORK_BYZANTIUM": 2000, + "HIVE_FORK_CONSTANTINOPLE": 2000, + "HIVE_FORK_PETERSBURG": 2000, + "HIVE_FORK_ISTANBUL": 2000, + "HIVE_FORK_BERLIN": 2000, + "HIVE_FORK_LONDON": 2000, + }, + "EIP150": { + "HIVE_FORK_HOMESTEAD": 0, + "HIVE_FORK_TANGERINE": 0, + "HIVE_FORK_SPURIOUS": 2000, + "HIVE_FORK_BYZANTIUM": 2000, + "HIVE_FORK_CONSTANTINOPLE": 2000, + "HIVE_FORK_PETERSBURG": 2000, + "HIVE_FORK_ISTANBUL": 2000, + "HIVE_FORK_BERLIN": 2000, + "HIVE_FORK_LONDON": 2000, + }, + "EIP158": { + "HIVE_FORK_HOMESTEAD": 0, + "HIVE_FORK_TANGERINE": 0, + "HIVE_FORK_SPURIOUS": 0, + "HIVE_FORK_BYZANTIUM": 2000, + "HIVE_FORK_CONSTANTINOPLE": 2000, + "HIVE_FORK_PETERSBURG": 2000, + "HIVE_FORK_ISTANBUL": 2000, + "HIVE_FORK_BERLIN": 2000, + "HIVE_FORK_LONDON": 2000, + }, + "Byzantium": { + "HIVE_FORK_HOMESTEAD": 0, + "HIVE_FORK_TANGERINE": 0, + "HIVE_FORK_SPURIOUS": 0, + "HIVE_FORK_BYZANTIUM": 0, + "HIVE_FORK_CONSTANTINOPLE": 2000, + "HIVE_FORK_PETERSBURG": 2000, + "HIVE_FORK_ISTANBUL": 2000, + "HIVE_FORK_BERLIN": 2000, + "HIVE_FORK_LONDON": 2000, + }, + "Constantinople": { + "HIVE_FORK_HOMESTEAD": 0, + "HIVE_FORK_TANGERINE": 0, + "HIVE_FORK_SPURIOUS": 0, + "HIVE_FORK_BYZANTIUM": 0, + "HIVE_FORK_CONSTANTINOPLE": 0, + "HIVE_FORK_PETERSBURG": 2000, + "HIVE_FORK_ISTANBUL": 2000, + "HIVE_FORK_BERLIN": 2000, + "HIVE_FORK_LONDON": 2000, + }, + "ConstantinopleFix": { + "HIVE_FORK_HOMESTEAD": 0, + "HIVE_FORK_TANGERINE": 0, + "HIVE_FORK_SPURIOUS": 0, + "HIVE_FORK_BYZANTIUM": 0, + "HIVE_FORK_CONSTANTINOPLE": 0, + "HIVE_FORK_PETERSBURG": 0, + "HIVE_FORK_ISTANBUL": 2000, + "HIVE_FORK_BERLIN": 2000, + "HIVE_FORK_LONDON": 2000, + }, + "Istanbul": { + "HIVE_FORK_HOMESTEAD": 0, + "HIVE_FORK_TANGERINE": 0, + "HIVE_FORK_SPURIOUS": 0, + "HIVE_FORK_BYZANTIUM": 0, + "HIVE_FORK_CONSTANTINOPLE": 0, + "HIVE_FORK_PETERSBURG": 0, + "HIVE_FORK_ISTANBUL": 0, + "HIVE_FORK_BERLIN": 2000, + "HIVE_FORK_LONDON": 2000, + }, + "Berlin": { + "HIVE_FORK_HOMESTEAD": 0, + "HIVE_FORK_TANGERINE": 0, + "HIVE_FORK_SPURIOUS": 0, + "HIVE_FORK_BYZANTIUM": 0, + "HIVE_FORK_CONSTANTINOPLE": 0, + "HIVE_FORK_PETERSBURG": 0, + "HIVE_FORK_ISTANBUL": 0, + "HIVE_FORK_BERLIN": 0, + "HIVE_FORK_LONDON": 2000, + }, + "FrontierToHomesteadAt5": { + "HIVE_FORK_HOMESTEAD": 5, + "HIVE_FORK_DAO_BLOCK": 2000, + "HIVE_FORK_TANGERINE": 2000, + "HIVE_FORK_SPURIOUS": 2000, + "HIVE_FORK_BYZANTIUM": 2000, + "HIVE_FORK_CONSTANTINOPLE": 2000, + "HIVE_FORK_PETERSBURG": 2000, + "HIVE_FORK_ISTANBUL": 2000, + "HIVE_FORK_BERLIN": 2000, + "HIVE_FORK_LONDON": 2000, + }, + "HomesteadToEIP150At5": { + "HIVE_FORK_HOMESTEAD": 0, + # "HIVE_FORK_DAO_BLOCK": 2000, + "HIVE_FORK_TANGERINE": 5, + "HIVE_FORK_SPURIOUS": 2000, + "HIVE_FORK_BYZANTIUM": 2000, + "HIVE_FORK_CONSTANTINOPLE": 2000, + "HIVE_FORK_PETERSBURG": 2000, + "HIVE_FORK_ISTANBUL": 2000, + "HIVE_FORK_BERLIN": 2000, + "HIVE_FORK_LONDON": 2000, + }, + "HomesteadToDaoAt5": { + "HIVE_FORK_HOMESTEAD": 0, + "HIVE_FORK_DAO_BLOCK": 5, + "HIVE_FORK_TANGERINE": 2000, + "HIVE_FORK_SPURIOUS": 2000, + "HIVE_FORK_BYZANTIUM": 2000, + "HIVE_FORK_CONSTANTINOPLE": 2000, + "HIVE_FORK_PETERSBURG": 2000, + "HIVE_FORK_ISTANBUL": 2000, + "HIVE_FORK_BERLIN": 2000, + "HIVE_FORK_LONDON": 2000, + }, + "EIP158ToByzantiumAt5": { + "HIVE_FORK_HOMESTEAD": 0, + # "HIVE_FORK_DAO_BLOCK": 2000, + "HIVE_FORK_TANGERINE": 0, + "HIVE_FORK_SPURIOUS": 0, + "HIVE_FORK_BYZANTIUM": 5, + "HIVE_FORK_CONSTANTINOPLE": 2000, + "HIVE_FORK_PETERSBURG": 2000, + "HIVE_FORK_ISTANBUL": 2000, + "HIVE_FORK_BERLIN": 2000, + "HIVE_FORK_LONDON": 2000, + }, + "ByzantiumToConstantinopleAt5": { + "HIVE_FORK_HOMESTEAD": 0, + # "HIVE_FORK_DAO_BLOCK": 2000, + "HIVE_FORK_TANGERINE": 0, + "HIVE_FORK_SPURIOUS": 0, + "HIVE_FORK_BYZANTIUM": 0, + "HIVE_FORK_CONSTANTINOPLE": 5, + "HIVE_FORK_PETERSBURG": 2000, + "HIVE_FORK_ISTANBUL": 2000, + "HIVE_FORK_BERLIN": 2000, + "HIVE_FORK_LONDON": 2000, + }, + "ByzantiumToConstantinopleFixAt5": { + "HIVE_FORK_HOMESTEAD": 0, + # "HIVE_FORK_DAO_BLOCK": 2000, + "HIVE_FORK_TANGERINE": 0, + "HIVE_FORK_SPURIOUS": 0, + "HIVE_FORK_BYZANTIUM": 0, + "HIVE_FORK_CONSTANTINOPLE": 5, + "HIVE_FORK_PETERSBURG": 5, + "HIVE_FORK_ISTANBUL": 2000, + "HIVE_FORK_BERLIN": 2000, + "HIVE_FORK_LONDON": 2000, + }, + "ConstantinopleFixToIstanbulAt5": { + "HIVE_FORK_HOMESTEAD": 0, + # "HIVE_FORK_DAO_BLOCK": 2000, + "HIVE_FORK_TANGERINE": 0, + "HIVE_FORK_SPURIOUS": 0, + "HIVE_FORK_BYZANTIUM": 0, + "HIVE_FORK_CONSTANTINOPLE": 0, + "HIVE_FORK_PETERSBURG": 0, + "HIVE_FORK_ISTANBUL": 5, + "HIVE_FORK_BERLIN": 2000, + "HIVE_FORK_LONDON": 2000, + }, + "IstanbulToBerlinAt5": { + "HIVE_FORK_HOMESTEAD": 0, + # "HIVE_FORK_DAO_BLOCK": 2000, + "HIVE_FORK_TANGERINE": 0, + "HIVE_FORK_SPURIOUS": 0, + "HIVE_FORK_BYZANTIUM": 0, + "HIVE_FORK_CONSTANTINOPLE": 0, + "HIVE_FORK_PETERSBURG": 0, + "HIVE_FORK_ISTANBUL": 0, + "HIVE_FORK_BERLIN": 5, + "HIVE_FORK_LONDON": 2000, + }, + "BerlinToLondonAt5": { + "HIVE_FORK_HOMESTEAD": 0, + # "HIVE_FORK_DAO_BLOCK": 2000, + "HIVE_FORK_TANGERINE": 0, + "HIVE_FORK_SPURIOUS": 0, + "HIVE_FORK_BYZANTIUM": 0, + "HIVE_FORK_CONSTANTINOPLE": 0, + "HIVE_FORK_PETERSBURG": 0, + "HIVE_FORK_ISTANBUL": 0, + "HIVE_FORK_BERLIN": 0, + "HIVE_FORK_LONDON": 5, + }, + "London": { + "HIVE_FORK_HOMESTEAD": 0, + "HIVE_FORK_TANGERINE": 0, + "HIVE_FORK_SPURIOUS": 0, + "HIVE_FORK_BYZANTIUM": 0, + "HIVE_FORK_CONSTANTINOPLE": 0, + "HIVE_FORK_PETERSBURG": 0, + "HIVE_FORK_ISTANBUL": 0, + "HIVE_FORK_BERLIN": 0, + "HIVE_FORK_LONDON": 0, + }, + "ArrowGlacierToMergeAtDiffC0000": { + "HIVE_FORK_HOMESTEAD": 0, + "HIVE_FORK_TANGERINE": 0, + "HIVE_FORK_SPURIOUS": 0, + "HIVE_FORK_BYZANTIUM": 0, + "HIVE_FORK_CONSTANTINOPLE": 0, + "HIVE_FORK_PETERSBURG": 0, + "HIVE_FORK_ISTANBUL": 0, + "HIVE_FORK_BERLIN": 0, + "HIVE_FORK_LONDON": 0, + "HIVE_TERMINAL_TOTAL_DIFFICULTY": 786432, + }, + "Merge": { + "HIVE_FORK_HOMESTEAD": 0, + "HIVE_FORK_TANGERINE": 0, + "HIVE_FORK_SPURIOUS": 0, + "HIVE_FORK_BYZANTIUM": 0, + "HIVE_FORK_CONSTANTINOPLE": 0, + "HIVE_FORK_PETERSBURG": 0, + "HIVE_FORK_ISTANBUL": 0, + "HIVE_FORK_BERLIN": 0, + "HIVE_FORK_LONDON": 0, + "HIVE_FORK_MERGE": 0, + "HIVE_TERMINAL_TOTAL_DIFFICULTY": 0, + }, + "Shanghai": { + "HIVE_FORK_HOMESTEAD": 0, + "HIVE_FORK_TANGERINE": 0, + "HIVE_FORK_SPURIOUS": 0, + "HIVE_FORK_BYZANTIUM": 0, + "HIVE_FORK_CONSTANTINOPLE": 0, + "HIVE_FORK_PETERSBURG": 0, + "HIVE_FORK_ISTANBUL": 0, + "HIVE_FORK_BERLIN": 0, + "HIVE_FORK_LONDON": 0, + "HIVE_FORK_MERGE": 0, + "HIVE_TERMINAL_TOTAL_DIFFICULTY": 0, + "HIVE_SHANGHAI_TIMESTAMP": 0, + }, + "MergeToShanghaiAtTime15k": { + "HIVE_FORK_HOMESTEAD": 0, + "HIVE_FORK_TANGERINE": 0, + "HIVE_FORK_SPURIOUS": 0, + "HIVE_FORK_BYZANTIUM": 0, + "HIVE_FORK_CONSTANTINOPLE": 0, + "HIVE_FORK_PETERSBURG": 0, + "HIVE_FORK_ISTANBUL": 0, + "HIVE_FORK_BERLIN": 0, + "HIVE_FORK_LONDON": 0, + "HIVE_FORK_MERGE": 0, + "HIVE_TERMINAL_TOTAL_DIFFICULTY": 0, + "HIVE_SHANGHAI_TIMESTAMP": 15000, + }, + "Cancun": { + "HIVE_FORK_HOMESTEAD": 0, + "HIVE_FORK_TANGERINE": 0, + "HIVE_FORK_SPURIOUS": 0, + "HIVE_FORK_BYZANTIUM": 0, + "HIVE_FORK_CONSTANTINOPLE": 0, + "HIVE_FORK_PETERSBURG": 0, + "HIVE_FORK_ISTANBUL": 0, + "HIVE_FORK_BERLIN": 0, + "HIVE_FORK_LONDON": 0, + "HIVE_FORK_MERGE": 0, + "HIVE_TERMINAL_TOTAL_DIFFICULTY": 0, + "HIVE_SHANGHAI_TIMESTAMP": 0, + "HIVE_CANCUN_TIMESTAMP": 0, + }, + "ShanghaiToCancunAtTime15k": { + "HIVE_FORK_HOMESTEAD": 0, + "HIVE_FORK_TANGERINE": 0, + "HIVE_FORK_SPURIOUS": 0, + "HIVE_FORK_BYZANTIUM": 0, + "HIVE_FORK_CONSTANTINOPLE": 0, + "HIVE_FORK_PETERSBURG": 0, + "HIVE_FORK_ISTANBUL": 0, + "HIVE_FORK_BERLIN": 0, + "HIVE_FORK_LONDON": 0, + "HIVE_FORK_MERGE": 0, + "HIVE_TERMINAL_TOTAL_DIFFICULTY": 0, + "HIVE_SHANGHAI_TIMESTAMP": 0, + "HIVE_CANCUN_TIMESTAMP": 15000, + }, + "Prague": { + "HIVE_FORK_HOMESTEAD": 0, + "HIVE_FORK_TANGERINE": 0, + "HIVE_FORK_SPURIOUS": 0, + "HIVE_FORK_BYZANTIUM": 0, + "HIVE_FORK_CONSTANTINOPLE": 0, + "HIVE_FORK_PETERSBURG": 0, + "HIVE_FORK_ISTANBUL": 0, + "HIVE_FORK_BERLIN": 0, + "HIVE_FORK_LONDON": 0, + "HIVE_FORK_MERGE": 0, + "HIVE_TERMINAL_TOTAL_DIFFICULTY": 0, + "HIVE_SHANGHAI_TIMESTAMP": 0, + "HIVE_CANCUN_TIMESTAMP": 0, + "HIVE_PRAGUE_TIMESTAMP": 0, + }, + "CancunToPragueAtTime15k": { + "HIVE_FORK_HOMESTEAD": 0, + "HIVE_FORK_TANGERINE": 0, + "HIVE_FORK_SPURIOUS": 0, + "HIVE_FORK_BYZANTIUM": 0, + "HIVE_FORK_CONSTANTINOPLE": 0, + "HIVE_FORK_PETERSBURG": 0, + "HIVE_FORK_ISTANBUL": 0, + "HIVE_FORK_BERLIN": 0, + "HIVE_FORK_LONDON": 0, + "HIVE_FORK_MERGE": 0, + "HIVE_TERMINAL_TOTAL_DIFFICULTY": 0, + "HIVE_SHANGHAI_TIMESTAMP": 0, + "HIVE_CANCUN_TIMESTAMP": 0, + "HIVE_PRAGUE_TIMESTAMP": 15000, + }, +} diff --git a/src/pytest_plugins/consume/hive_rulest_engine.py b/src/pytest_plugins/consume/hive_rulest_engine.py new file mode 100644 index 0000000000..32c0cf8986 --- /dev/null +++ b/src/pytest_plugins/consume/hive_rulest_engine.py @@ -0,0 +1,80 @@ +""" +Fork rules for clients ran within hive, starting from the Merge fork as +we are executing blocks using the Engine API. +""" + +# TODO: 1) Can we programmatically generate this? +# TODO: 2) Can we generate a single ruleset for both rlp and engine_api simulators. +client_fork_ruleset = { + "Merge": { + "HIVE_FORK_HOMESTEAD": 0, + "HIVE_FORK_TANGERINE": 0, + "HIVE_FORK_SPURIOUS": 0, + "HIVE_FORK_BYZANTIUM": 0, + "HIVE_FORK_CONSTANTINOPLE": 0, + "HIVE_FORK_PETERSBURG": 0, + "HIVE_FORK_ISTANBUL": 0, + "HIVE_FORK_BERLIN": 0, + "HIVE_FORK_LONDON": 0, + "HIVE_FORK_MERGE": 0, + "HIVE_TERMINAL_TOTAL_DIFFICULTY": 0, + }, + "Shanghai": { + "HIVE_FORK_HOMESTEAD": 0, + "HIVE_FORK_TANGERINE": 0, + "HIVE_FORK_SPURIOUS": 0, + "HIVE_FORK_BYZANTIUM": 0, + "HIVE_FORK_CONSTANTINOPLE": 0, + "HIVE_FORK_PETERSBURG": 0, + "HIVE_FORK_ISTANBUL": 0, + "HIVE_FORK_BERLIN": 0, + "HIVE_FORK_LONDON": 0, + "HIVE_FORK_MERGE": 0, + "HIVE_TERMINAL_TOTAL_DIFFICULTY": 0, + "HIVE_SHANGHAI_TIMESTAMP": 0, + }, + "MergeToShanghaiAtTime15k": { + "HIVE_FORK_HOMESTEAD": 0, + "HIVE_FORK_TANGERINE": 0, + "HIVE_FORK_SPURIOUS": 0, + "HIVE_FORK_BYZANTIUM": 0, + "HIVE_FORK_CONSTANTINOPLE": 0, + "HIVE_FORK_PETERSBURG": 0, + "HIVE_FORK_ISTANBUL": 0, + "HIVE_FORK_BERLIN": 0, + "HIVE_FORK_LONDON": 0, + "HIVE_FORK_MERGE": 0, + "HIVE_TERMINAL_TOTAL_DIFFICULTY": 0, + "HIVE_SHANGHAI_TIMESTAMP": 15000, + }, + "Cancun": { + "HIVE_FORK_HOMESTEAD": 0, + "HIVE_FORK_TANGERINE": 0, + "HIVE_FORK_SPURIOUS": 0, + "HIVE_FORK_BYZANTIUM": 0, + "HIVE_FORK_CONSTANTINOPLE": 0, + "HIVE_FORK_PETERSBURG": 0, + "HIVE_FORK_ISTANBUL": 0, + "HIVE_FORK_BERLIN": 0, + "HIVE_FORK_LONDON": 0, + "HIVE_FORK_MERGE": 0, + "HIVE_TERMINAL_TOTAL_DIFFICULTY": 0, + "HIVE_SHANGHAI_TIMESTAMP": 0, + "HIVE_CANCUN_TIMESTAMP": 0, + }, + "ShanghaiToCancunAtTime15k": { + "HIVE_FORK_HOMESTEAD": 0, + "HIVE_FORK_TANGERINE": 0, + "HIVE_FORK_SPURIOUS": 0, + "HIVE_FORK_BYZANTIUM": 0, + "HIVE_FORK_CONSTANTINOPLE": 0, + "HIVE_FORK_PETERSBURG": 0, + "HIVE_FORK_ISTANBUL": 0, + "HIVE_FORK_BERLIN": 0, + "HIVE_FORK_LONDON": 0, + "HIVE_FORK_MERGE": 0, + "HIVE_TERMINAL_TOTAL_DIFFICULTY": 0, + "HIVE_SHANGHAI_TIMESTAMP": 0, + "HIVE_CANCUN_TIMESTAMP": 15000, + }, +} diff --git a/src/pytest_plugins/consume/ini_files/pytest-consume-all.ini b/src/pytest_plugins/consume/ini_files/pytest-consume-all.ini new file mode 100644 index 0000000000..f6f49ea78d --- /dev/null +++ b/src/pytest_plugins/consume/ini_files/pytest-consume-all.ini @@ -0,0 +1,15 @@ +[pytest] +console_output_style = count +minversion = 7.0 +python_files = test_* +testpaths = tests_consume/test_direct.py tests_consume/test_via_rlp.py tests_consume/test_via_engine_api.py +addopts = + -rxXs + --tb short + -p pytest_plugins.consume.consume + -p pytest_plugins.consume.direct + -p pytest_plugins.consume.rlp + -p pytest_plugins.consume.engine + -p pytest_plugins.consume.simulator_common + -p pytest_plugins.pytest_hive.pytest_hive + -p pytest_plugins.test_help.test_help diff --git a/src/pytest_plugins/consume/ini_files/pytest-consume-direct.ini b/src/pytest_plugins/consume/ini_files/pytest-consume-direct.ini new file mode 100644 index 0000000000..1e72110ef4 --- /dev/null +++ b/src/pytest_plugins/consume/ini_files/pytest-consume-direct.ini @@ -0,0 +1,11 @@ +[pytest] +console_output_style = count +minversion = 7.0 +python_files = test_direct.py +testpaths = tests_consume/test_direct.py +addopts = + -rxXs + --tb short + -p pytest_plugins.consume.consume + -p pytest_plugins.consume.direct + -p pytest_plugins.test_help.test_help diff --git a/src/pytest_plugins/consume/ini_files/pytest-consume-engine.ini b/src/pytest_plugins/consume/ini_files/pytest-consume-engine.ini new file mode 100644 index 0000000000..f6b9522f10 --- /dev/null +++ b/src/pytest_plugins/consume/ini_files/pytest-consume-engine.ini @@ -0,0 +1,13 @@ +[pytest] +console_output_style = count +minversion = 7.0 +python_files = test_via_engine_api.py +testpaths = tests_consume/test_via_engine_api.py +addopts = + -rxXs + --tb short + -p pytest_plugins.consume.consume + -p pytest_plugins.consume.engine + -p pytest_plugins.consume.simulator_common + -p pytest_plugins.pytest_hive.pytest_hive + -p pytest_plugins.test_help.test_help diff --git a/src/pytest_plugins/consume/ini_files/pytest-consume-rlp.ini b/src/pytest_plugins/consume/ini_files/pytest-consume-rlp.ini new file mode 100644 index 0000000000..5e1288403a --- /dev/null +++ b/src/pytest_plugins/consume/ini_files/pytest-consume-rlp.ini @@ -0,0 +1,13 @@ +[pytest] +console_output_style = count +minversion = 7.0 +python_files = test_via_rlp.py +testpaths = tests_consume/test_via_rlp.py +addopts = + -rxXs + --tb short + -p pytest_plugins.consume.consume + -p pytest_plugins.consume.rlp + -p pytest_plugins.consume.simulator_common + -p pytest_plugins.pytest_hive.pytest_hive + -p pytest_plugins.test_help.test_help diff --git a/src/pytest_plugins/consume/rlp.py b/src/pytest_plugins/consume/rlp.py new file mode 100644 index 0000000000..ec00f9b4e3 --- /dev/null +++ b/src/pytest_plugins/consume/rlp.py @@ -0,0 +1,24 @@ +""" +A hive simulator that executes test fixtures in the blockchain test format +against clients by providing them a genesis state and RLP-encoded blocks +that they consume upon start-up. + +Implemented using the pytest framework as a pytest plugin. +""" +import pytest + + +@pytest.fixture(scope="session") +def test_suite_name() -> str: + """ + The name of the hive test suite used in this simulator. + """ + return "EEST Consume Blocks via RLP" + + +@pytest.fixture(scope="session") +def test_suite_description() -> str: + """ + The description of the hive test suite used in this simulator. + """ + return "Execute blockchain tests by providing RLP-encoded blocks to a client upon start-up." diff --git a/src/pytest_plugins/consume/simulator_common.py b/src/pytest_plugins/consume/simulator_common.py new file mode 100644 index 0000000000..e8f4b7d2f7 --- /dev/null +++ b/src/pytest_plugins/consume/simulator_common.py @@ -0,0 +1,38 @@ +""" +A pytest plugin containing common functionality for executing blockchain test +fixtures in Hive simulators (RLP and Engine API). +""" + +from pathlib import Path + +import pytest + +from ethereum_test_tools.spec.blockchain.types import Fixture +from ethereum_test_tools.spec.consume.types import TestCaseIndexFile, TestCaseStream +from ethereum_test_tools.spec.file.types import BlockchainFixtures +from pytest_plugins.consume.consume import JsonSource + +TestCase = TestCaseIndexFile | TestCaseStream + + +@pytest.fixture(scope="function") +def fixture(fixture_source: JsonSource, test_case: TestCase) -> Fixture: + """ + Return the blockchain fixture's pydantic model for the current test case. + + The fixture is either already available within the test case (if consume + is taking input on stdin) or loaded from the fixture json file if taking + input from disk (fixture directory with index file). + """ + if fixture_source == "stdin": + assert isinstance(test_case, TestCaseStream), "Expected a stream test case" + assert isinstance(test_case.fixture, Fixture), "Expected a blockchain test fixture" + fixture = test_case.fixture + else: + assert isinstance(test_case, TestCaseIndexFile), "Expected an index file test case" + # TODO: Optimize, json files will be loaded multiple times. This pytest fixture + # is executed per test case, and a fixture json will contain multiple test cases. + # Use cache fixtures as for statetest in consume direct? + fixtures = BlockchainFixtures.from_file(Path(fixture_source) / test_case.json_path) + fixture = fixtures[test_case.id] + return fixture diff --git a/src/pytest_plugins/forks/forks.py b/src/pytest_plugins/forks/forks.py index 18cbad1560..a040079ab4 100644 --- a/src/pytest_plugins/forks/forks.py +++ b/src/pytest_plugins/forks/forks.py @@ -149,6 +149,25 @@ def add_values(self, metafunc: Metafunc, fork_parametrizer: ForkParametrizer) -> ] +def get_fork_range(forks: List[Fork], forks_from: Fork, forks_until: Fork) -> List[Fork]: + """ + Get the fork range from forks_from to forks_until. + """ + return [ + next_fork for next_fork in forks if next_fork <= forks_until and next_fork >= forks_from + ] + + +def get_last_descendant(forks: List[Fork], fork: Fork) -> Fork: + """ + Get the last descendant of a class in the inheritance hierarchy. + """ + for next_fork in reversed(forks): + if next_fork >= fork: + return next_fork + return fork + + @pytest.hookimpl(tryfirst=True) def pytest_configure(config): """ @@ -176,19 +195,8 @@ def pytest_configure(config): for d in fork_covariant_descriptors: config.addinivalue_line("markers", f"{d.marker_name}: {d.description}") - def get_fork_option(config, option_name): - """Post-process get option to allow for external fork conditions.""" - option = config.getoption(option_name) - return "Paris" if option == "Merge" else option - - single_fork = get_fork_option(config, "single_fork") - forks_from = get_fork_option(config, "forks_from") - forks_until = get_fork_option(config, "forks_until") - show_fork_help = config.getoption("show_fork_help") - - config.all_forks = [fork for fork in get_forks() if not fork.ignore()] - config.fork_map = {fork.name(): fork for fork in config.all_forks} - config.fork_names = list(config.fork_map.keys()) + config.forks = [fork for fork in get_forks() if not fork.ignore()] + config.fork_names = [fork.name() for fork in config.forks] available_forks_help = textwrap.dedent( f"""\ @@ -202,6 +210,31 @@ def get_fork_option(config, option_name): {", ".join([fork.name() for fork in get_transition_forks()])} """ ) + + def get_fork_option(config, option_name: str, parameter_name: str) -> Fork | None: + """Post-process get option to allow for external fork conditions.""" + option = config.getoption(option_name) + if not option: + return None + if option == "Merge": + option = "Paris" + for fork in get_forks(): + if option == fork.name(): + return fork + print( + f"Error: Unsupported fork provided to {parameter_name}:", + option, + "\n", + file=sys.stderr, + ) + print(available_forks_help, file=sys.stderr) + pytest.exit("Invalid command-line options.", returncode=pytest.ExitCode.USAGE_ERROR) + + single_fork = get_fork_option(config, "single_fork", "--fork") + forks_from = get_fork_option(config, "forks_from", "--from") + forks_until = get_fork_option(config, "forks_until", "--until") + show_fork_help = config.getoption("show_fork_help") + dev_forks_help = textwrap.dedent( "To run tests for a fork under active development, it must be " "specified explicitly via --forks-until=FORK.\n" @@ -213,11 +246,6 @@ def get_fork_option(config, option_name): print(dev_forks_help) pytest.exit("After displaying help.", returncode=0) - if single_fork and single_fork not in config.fork_map.keys(): - print("Error: Unsupported fork provided to --fork:", single_fork, "\n", file=sys.stderr) - print(available_forks_help, file=sys.stderr) - pytest.exit("Invalid command-line options.", returncode=pytest.ExitCode.USAGE_ERROR) - if single_fork and (forks_from or forks_until): print( "Error: --fork cannot be used in combination with --from or --until", file=sys.stderr @@ -229,27 +257,16 @@ def get_fork_option(config, option_name): forks_until = single_fork else: if not forks_from: - forks_from = config.fork_names[0] + forks_from = config.forks[0] if not forks_until: - forks_until = get_deployed_forks()[-1].name() - - if forks_from not in config.fork_map.keys(): - print(f"Error: Unsupported fork provided to --from: {forks_from}\n", file=sys.stderr) - print(available_forks_help, file=sys.stderr) - pytest.exit("Invalid command-line options.", returncode=pytest.ExitCode.USAGE_ERROR) - - if forks_until not in config.fork_map.keys(): - print(f"Error: Unsupported fork provided to --until: {forks_until}\n", file=sys.stderr) - print(available_forks_help, file=sys.stderr) - pytest.exit("Invalid command-line options.", returncode=pytest.ExitCode.USAGE_ERROR) + forks_until = get_last_descendant(get_deployed_forks(), forks_from) - config.fork_range = config.fork_names[ - config.fork_names.index(forks_from) : config.fork_names.index(forks_until) + 1 - ] + config.fork_range = get_fork_range(config.forks, forks_from, forks_until) if not config.fork_range: print( - f"Error: --from {forks_from} --until {forks_until} creates an empty fork range.", + f"Error: --from {forks_from.name()} --until {forks_until.name()} " + "creates an empty fork range.", file=sys.stderr, ) pytest.exit( @@ -265,7 +282,7 @@ def get_fork_option(config, option_name): evm_bin = config.getoption("evm_bin") t8n = TransitionTool.from_binary_path(binary_path=evm_bin) config.unsupported_forks = [ - fork for fork in config.fork_range if not t8n.is_fork_supported(config.fork_map[fork]) + fork for fork in config.fork_range if not t8n.is_fork_supported(fork) ] @@ -276,7 +293,11 @@ def pytest_report_header(config, start_path): warning = "\033[93m" reset = "\033[39;49m" header = [ - (bold + f"Executing tests for: {', '.join(config.fork_range)} " + reset), + ( + bold + + f"Executing tests for: {', '.join([f.name() for f in config.fork_range])} " + + reset + ), ] if config.getoption("forks_until") is None: header += [ @@ -301,7 +322,7 @@ def get_validity_marker_args( metafunc: Metafunc, validity_marker_name: str, test_name: str, -) -> str | None: +) -> Fork | None: """Check and return the arguments specified to validity markers. Check that the validity markers: @@ -337,14 +358,16 @@ def get_validity_marker_args( f"'{test_name}': Too many arguments specified to '{validity_marker_name}' marker. " ) fork_name = validity_markers[0].args[0] - if fork_name not in metafunc.config.fork_names: # type: ignore - pytest.fail( - f"'{test_name}' specifies an invalid fork '{fork_name}' to the " - f"'{validity_marker_name}'. " - f"List of valid forks: {', '.join(metafunc.config.fork_names)}" # type: ignore - ) - return fork_name + for fork in metafunc.config.forks: # type: ignore + if fork.name() == fork_name: + return fork + + pytest.fail( + f"'{test_name}' specifies an invalid fork '{fork_name}' to the " + f"'{validity_marker_name}'. " + f"List of valid forks: {', '.join(metafunc.config.fork_names)}" # type: ignore + ) def pytest_generate_tests(metafunc): @@ -373,24 +396,16 @@ def pytest_generate_tests(metafunc): if valid_at_transition_to: if valid_at_transition_to in metafunc.config.fork_range: - to_fork = metafunc.config.fork_map[valid_at_transition_to] - intersection_range = transition_fork_to(to_fork) + intersection_range = transition_fork_to(valid_at_transition_to) else: if not valid_from: - valid_from = metafunc.config.fork_names[0] + valid_from = metafunc.config.forks[0] if not valid_until: - valid_until = metafunc.config.fork_names[-1] + valid_until = get_last_descendant(metafunc.config.fork_range, valid_from) - test_fork_range = set( - metafunc.config.fork_names[ - metafunc.config.fork_names.index(valid_from) : metafunc.config.fork_names.index( - valid_until - ) - + 1 - ] - ) + test_fork_range = get_fork_range(metafunc.config.forks, valid_from, valid_until) if not test_fork_range: pytest.fail( @@ -401,10 +416,8 @@ def pytest_generate_tests(metafunc): f"@pytest.mark.valid_until ({valid_until})." ) - intersection_range = list(set(metafunc.config.fork_range) & test_fork_range) - + intersection_range = list(set(metafunc.config.fork_range) & set(test_fork_range)) intersection_range.sort(key=metafunc.config.fork_range.index) - intersection_range = [metafunc.config.fork_map[fork] for fork in intersection_range] if "fork" in metafunc.fixturenames: if not intersection_range: diff --git a/src/pytest_plugins/py.typed b/src/pytest_plugins/py.typed new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/pytest_plugins/pytest_hive/pytest_hive.py b/src/pytest_plugins/pytest_hive/pytest_hive.py new file mode 100644 index 0000000000..28537675d5 --- /dev/null +++ b/src/pytest_plugins/pytest_hive/pytest_hive.py @@ -0,0 +1,139 @@ +""" +A pytest plugin providing common functionality for Hive simulators. + +Simulators using this plugin must define two pytest fixtures: + +1. `test_suite_name`: The name of the test suite. +2. `test_suite_description`: The description of the test suite. + +These fixtures are used when creating the hive test suite. +""" +import os + +import pytest +from hive.client import ClientRole +from hive.simulation import Simulation +from hive.testing import HiveTest, HiveTestResult, HiveTestSuite + + +@pytest.fixture(scope="session") +def simulator(request): # noqa: D103 + return request.config.hive_simulator + + +@pytest.fixture(scope="session") +def test_suite(request, simulator: Simulation): + """ + Defines a Hive test suite and cleans up after all tests have run. + """ + try: + test_suite_name = request.getfixturevalue("test_suite_name") + test_suite_description = request.getfixturevalue("test_suite_description") + except pytest.FixtureLookupError: + pytest.exit( + "Error: The 'test_suite_name' and 'test_suite_description' fixtures are not defined " + "by the hive simulator pytest plugin using this ('test_suite') fixture!" + ) + + suite = simulator.start_suite(name=test_suite_name, description=test_suite_description) + # TODO: Can we share this fixture across all nodes using xdist? Hive uses different suites. + yield suite + suite.end() + + +def pytest_configure(config): # noqa: D103 + hive_simulator_url = os.environ.get("HIVE_SIMULATOR") + if hive_simulator_url is None: + pytest.exit( + "The HIVE_SIMULATOR environment variable is not set.\n\n" + "If running locally, start hive in --dev mode, for example:\n" + "./hive --dev --client go-ethereum\n\n" + "and set the HIVE_SIMULATOR to the reported URL. For example, in bash:\n" + "export HIVE_SIMULATOR=http://127.0.0.1:3000\n" + "or in fish:\n" + "set -x HIVE_SIMULATOR http://127.0.0.1:3000" + ) + # TODO: Try and get these into fixtures; this is only here due to the "dynamic" parametrization + # of client_type with hive_execution_clients. + config.hive_simulator_url = hive_simulator_url + config.hive_simulator = Simulation(url=hive_simulator_url) + try: + config.hive_execution_clients = config.hive_simulator.client_types( + role=ClientRole.ExecutionClient + ) + except Exception as e: + message = ( + f"Error connecting to hive simulator at {hive_simulator_url}.\n\n" + "Did you forget to start hive in --dev mode?\n" + "./hive --dev --client go-ethereum\n\n" + ) + if config.option.verbose > 0: + message += f"Error details:\n{str(e)}" + else: + message += "Re-run with -v for more details." + pytest.exit(message) + + +@pytest.hookimpl(trylast=True) +def pytest_report_header(config, start_path): + """ + Add lines to pytest's console output header. + """ + if config.option.collectonly: + return + return [f"hive simulator: {config.hive_simulator_url}"] + + +@pytest.hookimpl(tryfirst=True, hookwrapper=True) +def pytest_runtest_makereport(item, call): + """ + Make the setup, call, and teardown results available in the teardown phase of + a test fixture (i.e., after yield has been called). + + This is used to get the test result and pass it to the hive test suite. + + Available as: + - result_setup - setup result + - result_call - test result + - result_teardown - teardown result + """ + outcome = yield + rep = outcome.get_result() + setattr(item, f"result_{rep.when}", rep) + + +@pytest.fixture +def hive_test(request, test_suite: HiveTestSuite): + """ + Propagate the pytest test case and its result to the hive server. + """ + test_parameter_string = request.node.nodeid.split("[")[-1].rstrip("]") # test fixture name + test: HiveTest = test_suite.start_test( + # TODO: pass test case documentation when available + name=test_parameter_string, + description="TODO: This should come from the '_info' field.", + ) + yield test + try: + # TODO: Handle xfail/skip, does this work with run=False? + if hasattr(request.node, "result_call") and request.node.result_call.passed: + test_passed = True + test_result_details = "Test passed." + elif hasattr(request.node, "result_call") and not request.node.result_call.passed: + test_passed = False + test_result_details = request.node.result_call.longreprtext + elif hasattr(request.node, "result_setup") and not request.node.result_setup.passed: + test_passed = False + test_result_details = "Test setup failed.\n" + request.node.result_setup.longreprtext + elif hasattr(request.node, "result_teardown") and not request.node.result_teardown.passed: + test_passed = False + test_result_details = ( + "Test teardown failed.\n" + request.node.result_teardown.longreprtext + ) + else: + test_passed = False + test_result_details = "Test failed for unknown reason (setup or call status unknown)." + except Exception as e: + test_passed = False + test_result_details = f"Exception whilst processing test result: {str(e)}" + test.end(result=HiveTestResult(test_pass=test_passed, details=test_result_details)) diff --git a/src/pytest_plugins/test_filler/test_filler.py b/src/pytest_plugins/test_filler/test_filler.py index 911f61ad30..9c44d353aa 100644 --- a/src/pytest_plugins/test_filler/test_filler.py +++ b/src/pytest_plugins/test_filler/test_filler.py @@ -6,11 +6,13 @@ writes the generated fixtures to file. """ +import os import warnings from pathlib import Path from typing import Generator, List, Optional, Type import pytest +from pytest_metadata.plugin import metadata_key # type: ignore from ethereum_test_forks import ( Fork, @@ -24,6 +26,22 @@ from pytest_plugins.spec_version_checker.spec_version_checker import EIPSpecTestItem +def default_output_directory() -> str: + """ + The default directory to store the generated test fixtures. Defined as a + function to allow for easier testing. + """ + return "./fixtures" + + +def default_html_report_filename() -> str: + """ + The default file to store the generated HTML test report. Defined as a + function to allow for easier testing. + """ + return "report_fill.html" + + def pytest_addoption(parser): """ Adds command-line options to pytest. @@ -95,8 +113,11 @@ def pytest_addoption(parser): "--output", action="store", dest="output", - default="./fixtures/", - help="Directory to store the generated test fixtures. Can be deleted.", + default=default_output_directory(), + help=( + "Directory to store the generated test fixtures. Can be deleted. " + f"Default: '{default_output_directory()}'." + ), ) test_group.addoption( "--flat-output", @@ -115,6 +136,16 @@ def pytest_addoption(parser): "file. This can be used to increase the granularity of --verify-fixtures." ), ) + test_group.addoption( + "--no-html", + action="store_true", + dest="disable_html", + default=False, + help=( + "Don't generate an HTML test report (in the output directory). " + "The --html flag can be used to specify a different path." + ), + ) debug_group = parser.getgroup("debug", "Arguments defining debug behavior") debug_group.addoption( @@ -130,10 +161,18 @@ def pytest_addoption(parser): @pytest.hookimpl(tryfirst=True) def pytest_configure(config): """ - Register the plugin's custom markers and process command-line options. + Pytest hook called after command line options have been parsed and before + test collection begins. + + Couple of notes: + 1. Register the plugin's custom markers and process command-line options. + + Custom marker registration: + https://docs.pytest.org/en/7.1.x/how-to/writing_plugins.html#registering-custom-markers - Custom marker registration: - https://docs.pytest.org/en/7.1.x/how-to/writing_plugins.html#registering-custom-markers + 2. `@pytest.hookimpl(tryfirst=True)` is applied to ensure that this hook is + called before the pytest-html plugin's pytest_configure to ensure that + it uses the modified `htmlpath` option. """ for fixture_format in FixtureFormats: config.addinivalue_line( @@ -143,7 +182,6 @@ def pytest_configure(config): f"{FixtureFormats.get_format_description(fixture_format)}" ), ) - config.addinivalue_line( "markers", "yul_test: a test case that compiles Yul code.", @@ -154,6 +192,11 @@ def pytest_configure(config): ) if config.option.collectonly: return + if not config.getoption("disable_html") and config.getoption("htmlpath") is None: + # generate an html report by default, unless explicitly disabled + config.option.htmlpath = os.path.join( + config.getoption("output"), default_html_report_filename() + ) # Instantiate the transition tool here to check that the binary path/trace option is valid. # This ensures we only raise an error once, if appropriate, instead of for every test. t8n = TransitionTool.from_binary_path( @@ -176,15 +219,125 @@ def pytest_configure(config): returncode=pytest.ExitCode.USAGE_ERROR, ) + config.stash[metadata_key]["Versions"] = { + "t8n": t8n.version(), + "solc": str(config.solc_version), + } + command_line_args = "fill " + " ".join(config.invocation_params.args) + config.stash[metadata_key]["Command-line args"] = f"{command_line_args}" + @pytest.hookimpl(trylast=True) def pytest_report_header(config, start_path): """Add lines to pytest's console output header""" if config.option.collectonly: return - binary_path = config.getoption("evm_bin") - t8n = TransitionTool.from_binary_path(binary_path=binary_path) - return [f"{t8n.version()}, solc version {config.solc_version}"] + t8n_version = config.stash[metadata_key]["Versions"]["t8n"] + solc_version = config.stash[metadata_key]["Versions"]["solc"] + return [(f"{t8n_version}, {solc_version}")] + + +def pytest_report_teststatus(report, config): + """ + Disable test session progress report if we're writing the JSON fixtures to + stdout to be read by a consume command on stdin. I.e., don't write this + type of output to the console: + + ```text + ...x... + ``` + """ + if config.getoption("output") == "stdout": + return report.outcome, "", report.outcome.upper() + + +def pytest_metadata(metadata): + """ + Add or remove metadata to/from the pytest report. + """ + metadata.pop("JAVA_HOME", None) + + +def pytest_html_results_table_header(cells): + """ + Customize the table headers of the HTML report table. + """ + cells.insert(3, 'JSON Fixture File') + cells.insert(4, 'EVM Dump Dir') + del cells[-1] # Remove the "Links" column + + +def pytest_html_results_table_row(report, cells): + """ + Customize the table rows of the HTML report table. + """ + if hasattr(report, "user_properties"): + user_props = dict(report.user_properties) + if ( + report.passed + and "fixture_path_absolute" in user_props + and "fixture_path_relative" in user_props + ): + fixture_path_absolute = user_props["fixture_path_absolute"] + fixture_path_relative = user_props["fixture_path_relative"] + fixture_path_link = ( + f'{fixture_path_relative}' + ) + cells.insert(3, f"{fixture_path_link}") + elif report.failed: + cells.insert(3, "Fixture unavailable") + if "evm_dump_dir" in user_props: + if user_props["evm_dump_dir"] is None: + cells.insert( + 4, "For t8n debug info use --evm-dump-dir=path --traces" + ) + else: + evm_dump_dir = user_props.get("evm_dump_dir") + if evm_dump_dir == "N/A": + evm_dump_entry = "N/A" + else: + evm_dump_entry = f'{evm_dump_dir}' + cells.insert(4, f"{evm_dump_entry}") + del cells[-1] # Remove the "Links" column + + +@pytest.hookimpl(hookwrapper=True) +def pytest_runtest_makereport(item, call): + """ + This hook is called when each test is run and a report is being made. + + Make each test's fixture json path available to the test report via + user_properties. + """ + outcome = yield + report = outcome.get_result() + + if call.when == "call": + if hasattr(item.config, "fixture_path_absolute") and hasattr( + item.config, "fixture_path_relative" + ): + report.user_properties.append( + ("fixture_path_absolute", item.config.fixture_path_absolute) + ) + report.user_properties.append( + ("fixture_path_relative", item.config.fixture_path_relative) + ) + if hasattr(item.config, "evm_dump_dir") and hasattr(item.config, "fixture_format"): + if item.config.fixture_format in [ + "state_test", + "blockchain_test", + "blockchain_test_hive", + ]: + report.user_properties.append(("evm_dump_dir", item.config.evm_dump_dir)) + else: + report.user_properties.append(("evm_dump_dir", "N/A")) # not yet for EOF + + +def pytest_html_report_title(report): + """ + Set the HTML report title (pytest-html plugin). + """ + report.title = "Fill Test Report" @pytest.fixture(autouse=True, scope="session") @@ -284,11 +437,17 @@ def dump_dir_parameter_level( Example with --evm-dump-dir=/tmp/evm: -> /tmp/evm/shanghai__eip3855_push0__test_push0__test_push0_key_sstore/fork_shanghai/ """ - return node_to_test_info(request.node).get_dump_dir_path( + evm_dump_dir = node_to_test_info(request.node).get_dump_dir_path( base_dump_dir, filler_path, level="test_parameter", ) + # NOTE: Use str for compatibility with pytest-dist + if evm_dump_dir: + request.node.config.evm_dump_dir = str(evm_dump_dir) + else: + request.node.config.evm_dump_dir = None + return evm_dump_dir def get_fixture_collection_scope(fixture_name, config): @@ -297,6 +456,8 @@ def get_fixture_collection_scope(fixture_name, config): See: https://docs.pytest.org/en/stable/how-to/fixtures.html#dynamic-scope """ + if config.getoption("output") == "stdout": + return "session" if config.getoption("single_fixture_per_file"): return "function" return "module" @@ -368,7 +529,12 @@ class so that upon instantiation within the test case, it provides the pytest.fail( f"{request.node.name}: Expected one argument in 'compile_yul_with' marker." ) - solc_target_fork = request.config.fork_map[marker.args[0]] + for fork in request.config.forks: + if fork.name() == marker.args[0]: + solc_target_fork = fork + break + else: + pytest.fail(f"{request.node.name}: Fork {marker.args[0]} not found in forks list.") assert solc_target_fork in get_forks_with_solc_support(request.config.solc_version) else: solc_target_fork = get_closest_fork_with_solc_support(fork, request.config.solc_version) @@ -427,7 +593,7 @@ def base_test_parametrizer_func( (see `pytest_parameter_name` in each implementation of BaseTest) in its function arguments. - When parametrizing, indirect must be used along with the fixture format as value. + When parametrize, indirect must be used along with the fixture format as value. """ fixture_format = request.param assert isinstance(fixture_format, FixtureFormats) @@ -444,11 +610,18 @@ def __init__(self, *args, **kwargs): ) fixture.fill_info(t8n, reference_spec) - fixture_collector.add_fixture( + fixture_path = fixture_collector.add_fixture( node_to_test_info(request.node), fixture, ) + # NOTE: Use str for compatibility with pytest-dist + request.node.config.fixture_path_absolute = str(fixture_path.absolute()) + request.node.config.fixture_path_relative = str( + fixture_path.relative_to(request.config.getoption("output")) + ) + request.node.config.fixture_format = fixture_format.value + return BaseTestWrapper return base_test_parametrizer_func diff --git a/src/pytest_plugins/test_filler/tests/test_test_filler.py b/src/pytest_plugins/test_filler/tests/test_test_filler.py index 8b6eb071e3..b1d262a26e 100644 --- a/src/pytest_plugins/test_filler/tests/test_test_filler.py +++ b/src/pytest_plugins/test_filler/tests/test_test_filler.py @@ -8,6 +8,8 @@ import pytest +from pytest_plugins.test_filler.test_filler import default_output_directory + # flake8: noqa def get_all_files_in_directory(base_dir): # noqa: D103 @@ -477,6 +479,7 @@ def test_fixture_output_based_on_command_line_args( testdir.copy_example(name="pytest.ini") args.append("-v") + args.append("--no-html") result = testdir.runpytest(*args) result.assert_outcomes( passed=total_test_count * 3, @@ -487,7 +490,7 @@ def test_fixture_output_based_on_command_line_args( if "--output" in args: output_dir = Path(args[args.index("--output") + 1]).absolute() else: - output_dir = Path("fixtures").absolute() + output_dir = Path(default_output_directory()).absolute() assert output_dir.exists() all_files = get_all_files_in_directory(output_dir) diff --git a/src/pytest_plugins/test_help/test_help.py b/src/pytest_plugins/test_help/test_help.py index 2a8755dcc0..fb71a5d5de 100644 --- a/src/pytest_plugins/test_help/test_help.py +++ b/src/pytest_plugins/test_help/test_help.py @@ -4,6 +4,7 @@ """ import argparse +from pathlib import Path import pytest @@ -18,7 +19,10 @@ def pytest_addoption(parser): action="store_true", dest="show_test_help", default=False, - help="Only show help options specific to execution-spec-tests and exit.", + help=( + "Only show help options specific to a specific execution-spec-tests command and " + "exit." + ), ) @@ -37,14 +41,29 @@ def show_test_help(config): that group is specific to execution-spec-tests command-line arguments. """ - test_group_substrings = [ - "execution-spec-tests", - "evm", - "solc", - "fork range", - "filler location", - "defining debug", # the "debug" group in test_filler plugin. - ] + pytest_ini = Path(config.inifile) + if pytest_ini.name == "pytest.ini": + test_group_substrings = [ + "execution-spec-tests", + "evm", + "solc", + "fork range", + "filler location", + "defining debug", + ] + elif pytest_ini.name in [ + "pytest-consume-all.ini", + "pytest-consume-direct.ini", + "pytest-consume-rlp.ini", + "pytest-consume-engine.ini", + ]: + test_group_substrings = [ + "execution-spec-tests", + "consuming", + "defining debug", + ] + else: + raise ValueError("Unexpected pytest.ini file option generating test help.") test_parser = argparse.ArgumentParser() for group in config._parser.optparser._action_groups: diff --git a/stubs/jwt/__init__.pyi b/stubs/jwt/__init__.pyi new file mode 100644 index 0000000000..dee1918afd --- /dev/null +++ b/stubs/jwt/__init__.pyi @@ -0,0 +1,3 @@ +from .encode import encode + +__all__ = ("encode",) diff --git a/stubs/jwt/encode.pyi b/stubs/jwt/encode.pyi new file mode 100644 index 0000000000..3bfe608a1a --- /dev/null +++ b/stubs/jwt/encode.pyi @@ -0,0 +1,3 @@ +from typing import Any, Dict + +def encode(payload: Dict[Any, Any], key: bytes, algorithm: str) -> str: ... diff --git a/tests/cancun/eip4788_beacon_root/conftest.py b/tests/cancun/eip4788_beacon_root/conftest.py index e6bc99f3d0..7b577eca6e 100644 --- a/tests/cancun/eip4788_beacon_root/conftest.py +++ b/tests/cancun/eip4788_beacon_root/conftest.py @@ -101,7 +101,7 @@ def contract_call_account(call_type: Op, call_value: int, call_gas: int) -> Acco if call_type == Op.CALL or call_type == Op.CALLCODE: contract_call_code += Op.SSTORE( 0x00, # store the result of the contract call in storage[0] - call_type( + call_type( # type: ignore # https://github.com/ethereum/execution-spec-tests/issues/348 # noqa: E501 call_gas, Spec.BEACON_ROOTS_ADDRESS, call_value, @@ -115,7 +115,7 @@ def contract_call_account(call_type: Op, call_value: int, call_gas: int) -> Acco # delegatecall and staticcall use one less argument contract_call_code += Op.SSTORE( 0x00, - call_type( + call_type( # type: ignore # https://github.com/ethereum/execution-spec-tests/issues/348 # noqa: E501 call_gas, Spec.BEACON_ROOTS_ADDRESS, args_start, diff --git a/tests/cancun/eip4844_blobs/test_point_evaluation_precompile.py b/tests/cancun/eip4844_blobs/test_point_evaluation_precompile.py index c2443f71d3..062bb3918f 100644 --- a/tests/cancun/eip4844_blobs/test_point_evaluation_precompile.py +++ b/tests/cancun/eip4844_blobs/test_point_evaluation_precompile.py @@ -111,7 +111,7 @@ def precompile_caller_account(call_type: Op, call_gas: int) -> Account: if call_type == Op.CALL or call_type == Op.CALLCODE: precompile_caller_code += Op.SSTORE( 0, - call_type( + call_type( # type: ignore # https://github.com/ethereum/execution-spec-tests/issues/348 # noqa: E501 call_gas, Spec.POINT_EVALUATION_PRECOMPILE_ADDRESS, 0x00, @@ -125,7 +125,7 @@ def precompile_caller_account(call_type: Op, call_gas: int) -> Account: # Delegatecall and staticcall use one less argument precompile_caller_code += Op.SSTORE( 0, - call_type( + call_type( # type: ignore # https://github.com/ethereum/execution-spec-tests/issues/348 # noqa: E501 call_gas, Spec.POINT_EVALUATION_PRECOMPILE_ADDRESS, 0x00, diff --git a/tests/cancun/eip4844_blobs/test_point_evaluation_precompile_gas.py b/tests/cancun/eip4844_blobs/test_point_evaluation_precompile_gas.py index 8d8ccf5475..fe87c81763 100644 --- a/tests/cancun/eip4844_blobs/test_point_evaluation_precompile_gas.py +++ b/tests/cancun/eip4844_blobs/test_point_evaluation_precompile_gas.py @@ -89,7 +89,7 @@ def precompile_caller_account( + copy_opcode_cost(len(precompile_input)) ) if call_type == Op.CALL or call_type == Op.CALLCODE: - precompile_caller_code += call_type( + precompile_caller_code += call_type( # type: ignore # https://github.com/ethereum/execution-spec-tests/issues/348 # noqa: E501 call_gas, Spec.POINT_EVALUATION_PRECOMPILE_ADDRESS, 0x00, @@ -101,7 +101,7 @@ def precompile_caller_account( overhead_cost += (PUSH_OPERATIONS_COST * 6) + (CALLDATASIZE_COST * 1) elif call_type == Op.DELEGATECALL or call_type == Op.STATICCALL: # Delegatecall and staticcall use one less argument - precompile_caller_code += call_type( + precompile_caller_code += call_type( # type: ignore # https://github.com/ethereum/execution-spec-tests/issues/348 # noqa: E501 call_gas, Spec.POINT_EVALUATION_PRECOMPILE_ADDRESS, 0x00, diff --git a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py index 94f7989a53..5e459f2618 100644 --- a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py +++ b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py @@ -23,10 +23,10 @@ Storage, TestAddress, Transaction, - YulCompiler, compute_create2_address, compute_create_address, ) +from ethereum_test_tools.code.generators import Conditional from ethereum_test_tools.vm.opcode import Opcodes as Op REFERENCE_SPEC_GIT_PATH = "EIPS/eip-6780.md" @@ -69,28 +69,32 @@ def sendall_recipient_addresses() -> List[Address]: def selfdestruct_code_preset( *, sendall_recipient_addresses: List[Address], - yul: YulCompiler, -) -> SupportsBytes: + pre_bytecode: bytes, +) -> bytes: """Return a bytecode that self-destructs.""" + bytecode = pre_bytecode + + # First we register entry into the contract + bytecode += Op.SSTORE(0, Op.ADD(Op.SLOAD(0), 1)) + if len(sendall_recipient_addresses) != 1: # Load the recipient address from calldata, each test case needs to pass the addresses as # calldata - return yul( - f""" - {{ - sstore(0, add(sload(0), 1)) - let selfdestruct_recipient := calldataload(0) - if eq(selfdestruct_recipient, {SELF_ADDRESS}) {{ - // One sends to self - selfdestruct_recipient := address() - }} - if not(eq(selfdestruct_recipient, {NO_SELFDESTRUCT})) {{ - // zero is the sentinel value for not self-destructing - selfdestruct(selfdestruct_recipient) - sstore(0, 0) - }} - }} - """ # noqa: E272, E201, E202, E221 + bytecode += bytes( + Conditional( + # We avoid having the caller to give us our own address by checking + # against a constant that is a magic number + condition=Op.EQ(Op.CALLDATALOAD(0), SELF_ADDRESS), + if_true=Op.MSTORE(0, Op.ADDRESS()), + if_false=Op.MSTORE(0, Op.CALLDATALOAD(0)), + ) + ) + bytecode += bytes( + Conditional( + condition=Op.EQ(Op.MLOAD(0), NO_SELFDESTRUCT), + if_true=Op.STOP, + if_false=Op.SELFDESTRUCT(Op.MLOAD(0)), + ) ) else: # Hard-code the single only possible recipient address @@ -101,29 +105,29 @@ def selfdestruct_code_preset( # sendall_recipient = "address()" # TODO: Fix this pass - return yul( - f""" - {{ - sstore(0, add(sload(0), 1)) - selfdestruct({sendall_recipient_addresses[0]}) - sstore(0, 0) - }} - """ # noqa: E272, E201, E202, E221 - ) + bytecode += Op.SELFDESTRUCT(sendall_recipient_addresses[0]) + bytecode += Op.SSTORE(0, 0) + return bytecode + + +@pytest.fixture +def selfdestruct_pre_bytecode() -> bytes: + """Code run before attempting to self-destruct, by default it's empty.""" + return b"" @pytest.fixture def selfdestruct_code( + selfdestruct_pre_bytecode: bytes, sendall_recipient_addresses: List[Address], - yul: YulCompiler, -) -> SupportsBytes: +) -> bytes: """ Creates the default self-destructing bytecode, which can be modified by each test if necessary. """ return selfdestruct_code_preset( sendall_recipient_addresses=sendall_recipient_addresses, - yul=yul, + pre_bytecode=selfdestruct_pre_bytecode, ) @@ -181,8 +185,8 @@ def pre( selfdestruct_contract_initcode: SupportsBytes, selfdestruct_contract_address: Address, selfdestruct_contract_initial_balance: int, + selfdestruct_pre_bytecode: bytes, sendall_recipient_addresses: List[Address], - yul: YulCompiler, ) -> Dict[Address, Account]: """Pre-state of all tests""" pre = { @@ -200,7 +204,7 @@ def pre( pre[PRE_EXISTING_SELFDESTRUCT_ADDRESS] = Account( code=selfdestruct_code_preset( sendall_recipient_addresses=sendall_recipient_addresses, - yul=yul, + pre_bytecode=selfdestruct_pre_bytecode, ), balance=selfdestruct_contract_initial_balance, ) @@ -1327,3 +1331,177 @@ def test_delegatecall_from_pre_existing_contract_to_new_contract( ) state_test(env=env, pre=pre, post=post, tx=tx) + + +initcode = Op.RETURN(0, 1) + + +@pytest.mark.parametrize( + "selfdestruct_pre_bytecode", + [ + pytest.param( + Op.MSTORE(0, Op.PUSH32(initcode)) + + Op.POP(Op.CREATE(0, 32 - len(initcode), len(initcode))), + id="increase_nonce_by_create", + ) + ], +) +@pytest.mark.parametrize("create_opcode", [Op.CREATE, Op.CREATE2]) +@pytest.mark.parametrize("selfdestruct_contract_initial_balance", [0, 100_000]) +@pytest.mark.parametrize( + "call_times,sendall_recipient_addresses", + [ + pytest.param(1, [Address(0x1000)], id="single_call"), + pytest.param(5, [Address(0x1000)], id="multiple_calls_single beneficiary"), + ], +) +@pytest.mark.valid_from("Shanghai") +def test_create_selfdestruct_same_tx_increased_nonce( + state_test: StateTestFiller, + env: Environment, + pre: Dict[Address, Account], + entry_code_address: Address, + selfdestruct_code: SupportsBytes, + selfdestruct_contract_initcode: SupportsBytes, + selfdestruct_contract_address: Address, + sendall_recipient_addresses: List[Address], + initcode_copy_from_address: Address, + create_opcode: Op, + call_times: int, + selfdestruct_contract_initial_balance: int, +): + """ + Verify that a contract can self-destruct if it was created in the same transaction, even when + its nonce has been increased due to contract creation. + """ + # Our entry point is an initcode that in turn creates a self-destructing contract + entry_code_storage = Storage() + + # Create a dict to record the expected final balances + sendall_final_balances = dict( + zip(sendall_recipient_addresses, [0] * len(sendall_recipient_addresses)) + ) + selfdestruct_contract_current_balance = selfdestruct_contract_initial_balance + + # Bytecode used to create the contract, can be CREATE or CREATE2 + create_args = [ + 0, # Value + 0, # Offset + len(bytes(selfdestruct_contract_initcode)), # Length + ] + if create_opcode == Op.CREATE2: + # CREATE2 requires a salt argument + create_args.append(0) + create_bytecode = create_opcode(*create_args) + + # Entry code that will be executed, creates the contract and then calls it in the same tx + entry_code = ( + # Initcode is already deployed at `initcode_copy_from_address`, so just copy it + Op.EXTCODECOPY( + initcode_copy_from_address, + 0, + 0, + len(bytes(selfdestruct_contract_initcode)), + ) + # And we store the created address for verification purposes + + Op.SSTORE( + entry_code_storage.store_next(selfdestruct_contract_address), + create_bytecode, + ) + ) + + # Store the EXTCODE* properties of the created address + entry_code += Op.SSTORE( + entry_code_storage.store_next(len(bytes(selfdestruct_code))), + Op.EXTCODESIZE(selfdestruct_contract_address), + ) + + entry_code += Op.SSTORE( + entry_code_storage.store_next(keccak256(bytes(selfdestruct_code))), + Op.EXTCODEHASH(selfdestruct_contract_address), + ) + + # Call the self-destructing contract multiple times as required, increasing the wei sent each + # time + for i, sendall_recipient in zip(range(call_times), cycle(sendall_recipient_addresses)): + entry_code += Op.MSTORE(0, sendall_recipient) + entry_code += Op.SSTORE( + entry_code_storage.store_next(1), + Op.CALL( + Op.GASLIMIT, # Gas + selfdestruct_contract_address, # Address + i, # Value + 0, + 32, + 0, + 0, + ), + ) + selfdestruct_contract_current_balance += i + + # Balance is always sent to other contracts + if sendall_recipient != selfdestruct_contract_address: + sendall_final_balances[sendall_recipient] += selfdestruct_contract_current_balance + + # Self-destructing contract must always have zero balance after the call because the + # self-destruct always happens in the same transaction in this test + selfdestruct_contract_current_balance = 0 + + entry_code += Op.SSTORE( + entry_code_storage.store_next(0), + Op.BALANCE(selfdestruct_contract_address), + ) + + # Check the EXTCODE* properties of the self-destructing contract again + entry_code += Op.SSTORE( + entry_code_storage.store_next(len(bytes(selfdestruct_code))), + Op.EXTCODESIZE(selfdestruct_contract_address), + ) + + entry_code += Op.SSTORE( + entry_code_storage.store_next(keccak256(bytes(selfdestruct_code))), + Op.EXTCODEHASH(selfdestruct_contract_address), + ) + + # Lastly return zero so the entry point contract is created and we can retain the stored + # values for verification. + entry_code += Op.RETURN(max(len(bytes(selfdestruct_contract_initcode)), 32), 1) + + post: Dict[Address, Account] = { + entry_code_address: Account( + code="0x00", + storage=entry_code_storage, + ), + initcode_copy_from_address: Account( + code=selfdestruct_contract_initcode, + ), + } + + # Check the balances of the sendall recipients + for address, balance in sendall_final_balances.items(): + post[address] = Account(balance=balance, storage={0: 1}) + + # Check the new contracts created from the self-destructing contract were correctly created. + for address in [ + compute_create_address(selfdestruct_contract_address, i + 1) for i in range(call_times) + ]: + post[address] = Account( + code=b"\x00", + ) + + post[selfdestruct_contract_address] = Account.NONEXISTENT # type: ignore + + nonce = count() + tx = Transaction( + ty=0x0, + value=100_000, + data=entry_code, + chain_id=0x0, + nonce=next(nonce), + to=None, + gas_limit=100_000_000, + gas_price=10, + protected=False, + ) + + state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/prague/eip2537_bls_12_381_precompiles/__init__.py b/tests/prague/eip2537_bls_12_381_precompiles/__init__.py new file mode 100644 index 0000000000..2cede19405 --- /dev/null +++ b/tests/prague/eip2537_bls_12_381_precompiles/__init__.py @@ -0,0 +1,4 @@ +""" +abstract: Tests [EIP-2537: Precompile for BLS12-381 curve operations](https://eips.ethereum.org/EIPS/eip-2537) + Tests for [EIP-2537: Precompile for BLS12-381 curve operations](https://eips.ethereum.org/EIPS/eip-2537). +""" # noqa: E501 diff --git a/tests/prague/eip2537_bls_12_381_precompiles/conftest.py b/tests/prague/eip2537_bls_12_381_precompiles/conftest.py new file mode 100644 index 0000000000..454ca4baa2 --- /dev/null +++ b/tests/prague/eip2537_bls_12_381_precompiles/conftest.py @@ -0,0 +1,177 @@ +""" +Shared pytest definitions local to EIP-2537 tests. +""" +from typing import SupportsBytes + +import pytest +from ethereum.crypto.hash import keccak256 + +from ethereum_test_tools import Storage, TestAddress, Transaction +from ethereum_test_tools.vm import Opcodes as Op + +from .spec import GAS_CALCULATION_FUNCTION_MAP + + +@pytest.fixture +def precompile_gas(precompile_address: int, input: bytes) -> int: + """Gas cost for the precompile.""" + return GAS_CALCULATION_FUNCTION_MAP[precompile_address](len(input)) + + +@pytest.fixture +def precompile_gas_modifier() -> int: + """ + Used to modify the gas passed to the precompile, for testing purposes. + + By default the call is made with the exact gas amount required for the given opcode, + but when this fixture is overridden, the gas amount can be modified to, e.g., test + a lower amount and test if the precompile call fails. + """ + return 0 + + +@pytest.fixture +def call_opcode() -> Op: + """ + Type of call used to call the precompile. + + By default it is Op.CALL, but it can be overridden in the test. + """ + return Op.CALL + + +@pytest.fixture +def call_contract_post_storage() -> Storage: + """ + Storage of the test contract after the transaction is executed. + Note: Fixture `call_contract_code` fills the actual expected storage values. + """ + return Storage() + + +@pytest.fixture +def call_succeeds( + expected_output: bytes | SupportsBytes, +) -> bool: + """ + By default, depending on the expected output, we can deduce if the call is expected to succeed + or fail. + """ + return len(bytes(expected_output)) > 0 + + +@pytest.fixture +def call_contract_code( + precompile_address: int, + precompile_gas: int, + precompile_gas_modifier: int, + expected_output: bytes | SupportsBytes, + call_succeeds: bool, + call_opcode: Op, + call_contract_post_storage: Storage, +) -> bytes: + """ + Code of the test contract. + + Args: + precompile_address: + Address of the precompile to call. + precompile_gas: + Gas cost for the precompile, which is automatically calculated by the `precompile_gas` + fixture, but can be overridden in the test. + precompile_gas_modifier: + Gas cost modifier for the precompile, which is automatically set to zero by the + `precompile_gas_modifier` fixture, but can be overridden in the test. + expected_output: + Expected output of the precompile call. This value is used to determine if the call is + expected to succeed or fail. + call_succeeds: + Boolean that indicates if the call is expected to succeed or fail. + call_opcode: + Type of call used to call the precompile (Op.CALL, Op.CALLCODE, Op.DELEGATECALL, + Op.STATICCALL). + call_contract_post_storage: + Storage of the test contract after the transaction is executed. + """ + expected_output = bytes(expected_output) + + assert call_opcode in [Op.CALL, Op.CALLCODE, Op.DELEGATECALL, Op.STATICCALL] + value = [0] if call_opcode in [Op.CALL, Op.CALLCODE] else [] + + code = ( + Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE()) + + Op.SSTORE( + call_contract_post_storage.store_next(call_succeeds), + call_opcode( + precompile_gas + precompile_gas_modifier, + precompile_address, + *value, # Optional, only used for CALL and CALLCODE. + 0, + Op.CALLDATASIZE(), + 0, + 0, + ), + ) + + Op.SSTORE( + call_contract_post_storage.store_next(len(expected_output)), + Op.RETURNDATASIZE(), + ) + ) + if call_succeeds: + # Add integrity check only if the call is expected to succeed. + code += Op.RETURNDATACOPY(0, 0, Op.RETURNDATASIZE()) + Op.SSTORE( + call_contract_post_storage.store_next(keccak256(expected_output)), + Op.SHA3(0, Op.RETURNDATASIZE()), + ) + + return code + + +@pytest.fixture +def call_contract_address() -> int: + """Address where the test contract will be deployed.""" + return 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + + +@pytest.fixture +def pre(call_contract_address: int, call_contract_code: bytes): + """Pre-allocation for every test.""" + return { + call_contract_address: { + "balance": 0, + "nonce": 1, + "code": call_contract_code, + }, + TestAddress: { + "balance": 1_000_000_000_000_000, + "nonce": 0, + }, + } + + +@pytest.fixture +def post(call_contract_address: int, call_contract_post_storage: Storage): + """Test expected post outcome.""" + return { + call_contract_address: { + "storage": call_contract_post_storage, + }, + } + + +@pytest.fixture +def tx_gas_limit(precompile_gas: int) -> int: + """ + Transaction gas limit used for the test (Can be overridden in the test). + """ + return 10_000_000 + precompile_gas + + +@pytest.fixture +def tx(input: bytes, tx_gas_limit: int, call_contract_address: int) -> Transaction: + """Transaction for the test.""" + return Transaction( + gas_limit=tx_gas_limit, + input=input, + to=call_contract_address, + ) diff --git a/tests/prague/eip2537_bls_12_381_precompiles/helpers.py b/tests/prague/eip2537_bls_12_381_precompiles/helpers.py new file mode 100644 index 0000000000..9030365ecb --- /dev/null +++ b/tests/prague/eip2537_bls_12_381_precompiles/helpers.py @@ -0,0 +1,78 @@ +""" +Helper functions for the EIP-2537 BLS12-381 precompiles tests. +""" +import os +from typing import Annotated, List + +import pytest +from pydantic import BaseModel, BeforeValidator, ConfigDict, RootModel, TypeAdapter +from pydantic.alias_generators import to_pascal + + +def current_python_script_directory(*args: str) -> str: + """ + Get the current Python script directory, optionally appending additional path components. + """ + return os.path.join(os.path.dirname(os.path.realpath(__file__)), *args) + + +class TestVector(BaseModel): + """ + Test vector for the BLS12-381 precompiles. + """ + + input: Annotated[bytes, BeforeValidator(bytes.fromhex)] + expected: Annotated[bytes, BeforeValidator(bytes.fromhex)] + gas: int + name: str + + model_config = ConfigDict(alias_generator=to_pascal) + + def to_pytest_param(self): + """ + Convert the test vector to a tuple that can be used as a parameter in a pytest test. + """ + return pytest.param(self.input, self.expected, id=self.name) + + +class FailTestVector(BaseModel): + """ + Test vector for the BLS12-381 precompiles. + """ + + input: Annotated[bytes, BeforeValidator(bytes.fromhex)] + expected_error: str + name: str + + model_config = ConfigDict(alias_generator=to_pascal) + + def to_pytest_param(self): + """ + Convert the test vector to a tuple that can be used as a parameter in a pytest test. + """ + return pytest.param(self.input, id=self.name) + + +class TestVectorList(RootModel): + """ + List of test vectors for the BLS12-381 precompiles. + """ + + root: List[TestVector | FailTestVector] + + +TestVectorListAdapter = TypeAdapter(TestVectorList) + + +def vectors_from_file(filename: str) -> List: + """ + Load test vectors from a file. + """ + with open( + current_python_script_directory( + "vectors", + filename, + ), + "rb", + ) as f: + return [v.to_pytest_param() for v in TestVectorListAdapter.validate_json(f.read()).root] diff --git a/tests/prague/eip2537_bls_12_381_precompiles/msm_discount_table.json b/tests/prague/eip2537_bls_12_381_precompiles/msm_discount_table.json new file mode 100644 index 0000000000..570d39244f --- /dev/null +++ b/tests/prague/eip2537_bls_12_381_precompiles/msm_discount_table.json @@ -0,0 +1 @@ +[0,1200,888,764,641,594,547,500,453,438,423,408,394,379,364,349,334,330,326,322,318,314,310,306,302,298,294,289,285,281,277,273,269,268,266,265,263,262,260,259,257,256,254,253,251,250,248,247,245,244,242,241,239,238,236,235,233,232,231,229,228,226,225,223,222,221,220,219,219,218,217,216,216,215,214,213,213,212,211,211,210,209,208,208,207,206,205,205,204,203,202,202,201,200,199,199,198,197,196,196,195,194,193,193,192,191,191,190,189,188,188,187,186,185,185,184,183,182,182,181,180,179,179,178,177,176,176,175,174] \ No newline at end of file diff --git a/tests/prague/eip2537_bls_12_381_precompiles/spec.py b/tests/prague/eip2537_bls_12_381_precompiles/spec.py new file mode 100644 index 0000000000..513a2ae9b2 --- /dev/null +++ b/tests/prague/eip2537_bls_12_381_precompiles/spec.py @@ -0,0 +1,266 @@ +""" +Defines EIP-2537 specification constants and functions. +""" +import json +from dataclasses import dataclass +from typing import Callable, List, Sized, SupportsBytes, Tuple + +from .helpers import current_python_script_directory + + +@dataclass(frozen=True) +class ReferenceSpec: + """ + Defines the reference spec version and git path. + """ + + git_path: str + version: str + + +ref_spec_2537 = ReferenceSpec("EIPS/eip-2537.md", "cd0f016ad0c4c68b8b1f5c502ef61ab9353b6e5e") + + +class BytesConcatenation(SupportsBytes, Sized): + """ + A class that can be concatenated with bytes. + """ + + def __len__(self) -> int: + """Returns the length of the object when converted to bytes.""" + return len(bytes(self)) + + def __add__(self, other: bytes | SupportsBytes) -> bytes: + """Concatenates the object with another bytes object.""" + return bytes(self) + bytes(other) + + def __radd__(self, other: bytes | SupportsBytes) -> bytes: + """Concatenates the object with another bytes object.""" + return bytes(other) + bytes(self) + + +@dataclass(frozen=True) +class FP(BytesConcatenation): + """Dataclass that defines a single element of Fp.""" + + x: int = 0 + + def __bytes__(self) -> bytes: + """Converts the field element to bytes.""" + return self.x.to_bytes(64, byteorder="big") + + +@dataclass(frozen=True) +class PointG1(BytesConcatenation): + """Dataclass that defines a single point in G1.""" + + x: int = 0 + y: int = 0 + + def __bytes__(self) -> bytes: + """Converts the point to bytes.""" + return self.x.to_bytes(64, byteorder="big") + self.y.to_bytes(64, byteorder="big") + + def __neg__(self): + """Negates the point.""" + return PointG1(self.x, Spec.P - self.y) + + +@dataclass(frozen=True) +class FP2(BytesConcatenation): + """Dataclass that defines a single element of Fp2.""" + + x: Tuple[int, int] = (0, 0) + + def __bytes__(self) -> bytes: + """Converts the field element to bytes.""" + return self.x[0].to_bytes(64, byteorder="big") + self.x[1].to_bytes(64, byteorder="big") + + +@dataclass(frozen=True) +class PointG2(BytesConcatenation): + """Dataclass that defines a single point in G2.""" + + x: Tuple[int, int] = (0, 0) + y: Tuple[int, int] = (0, 0) + + def __bytes__(self) -> bytes: + """Converts the point to bytes.""" + return ( + self.x[0].to_bytes(64, byteorder="big") + + self.x[1].to_bytes(64, byteorder="big") + + self.y[0].to_bytes(64, byteorder="big") + + self.y[1].to_bytes(64, byteorder="big") + ) + + def __neg__(self): + """Negates the point.""" + return PointG2(self.x, (Spec.P - self.y[0], Spec.P - self.y[1])) + + +@dataclass(frozen=True) +class Scalar(BytesConcatenation): + """Dataclass that defines a single scalar.""" + + x: int = 0 + + def __bytes__(self) -> bytes: + """Converts the scalar to bytes.""" + return self.x.to_bytes(32, byteorder="big") + + +with open(current_python_script_directory("msm_discount_table.json")) as f: + MSM_DISCOUNT_TABLE: List[int] = json.load(f) + assert type(MSM_DISCOUNT_TABLE) is list + + +@dataclass(frozen=True) +class Spec: + """ + Parameters from the EIP-2537 specifications as defined at + https://eips.ethereum.org/EIPS/eip-2537 + """ + + # Addresses + G1ADD = 0x0B + G1MUL = 0x0C + G1MSM = 0x0D + G2ADD = 0x0E + G2MUL = 0x0F + G2MSM = 0x10 + PAIRING = 0x11 + MAP_FP_TO_G1 = 0x12 + MAP_FP2_TO_G2 = 0x13 + + # Gas constants + G1ADD_GAS = 500 + G1MUL_GAS = 12_000 + G2ADD_GAS = 800 + G2MUL_GAS = 45_000 + MAP_FP_TO_G1_GAS = 5_500 + MAP_FP2_TO_G2_GAS = 75_000 + PAIRING_BASE_GAS = 65_000 + PAIRING_PER_PAIR_GAS = 43_000 + + # Other constants + B_COEFFICIENT = 0x04 + X = -0xD201000000010000 + Q = X**4 - X**2 + 1 + P = (X - 1) ** 2 * Q // 3 + X + LEN_PER_PAIR = len(PointG1() + PointG2()) + MSM_MULTIPLIER = 1_000 + MSM_DISCOUNT_TABLE = MSM_DISCOUNT_TABLE + + # Test constants (from https://github.com/ethereum/bls12-381-tests/tree/eip-2537) + P1 = PointG1( # random point in G1 + 0x112B98340EEE2777CC3C14163DEA3EC97977AC3DC5C70DA32E6E87578F44912E902CCEF9EFE28D4A78B8999DFBCA9426, # noqa: E501 + 0x186B28D92356C4DFEC4B5201AD099DBDEDE3781F8998DDF929B4CD7756192185CA7B8F4EF7088F813270AC3D48868A21, # noqa: E501 + ) + G1 = PointG1( + 0x17F1D3A73197D7942695638C4FA9AC0FC3688C4F9774B905A14E3A3F171BAC586C55E83FF97A1AEFFB3AF00ADB22C6BB, # noqa: E501 + 0x8B3F481E3AAA0F1A09E30ED741D8AE4FCF5E095D5D00AF600DB18CB2C04B3EDD03CC744A2888AE40CAA232946C5E7E1, # noqa: E501 + ) + # point at infinity in G1 + INF_G1 = PointG1(0, 0) + # random point in G2 + P2 = PointG2( + ( + 0x103121A2CEAAE586D240843A398967325F8EB5A93E8FEA99B62B9F88D8556C80DD726A4B30E84A36EEABAF3592937F27, # noqa: E501 + 0x86B990F3DA2AEAC0A36143B7D7C824428215140DB1BB859338764CB58458F081D92664F9053B50B3FBD2E4723121B68, # noqa: E501 + ), + ( + 0xF9E7BA9A86A8F7624AA2B42DCC8772E1AF4AE115685E60ABC2C9B90242167ACEF3D0BE4050BF935EED7C3B6FC7BA77E, # noqa: E501 + 0xD22C3652D0DC6F0FC9316E14268477C2049EF772E852108D269D9C38DBA1D4802E8DAE479818184C08F9A569D878451, # noqa: E501 + ), + ) + G2 = PointG2( + ( + 0x24AA2B2F08F0A91260805272DC51051C6E47AD4FA403B02B4510B647AE3D1770BAC0326A805BBEFD48056C8C121BDB8, # noqa: E501 + 0x13E02B6052719F607DACD3A088274F65596BD0D09920B61AB5DA61BBDC7F5049334CF11213945D57E5AC7D055D042B7E, # noqa: E501 + ), + ( + 0xCE5D527727D6E118CC9CDC6DA2E351AADFD9BAA8CBDD3A76D429A695160D12C923AC9CC3BACA289E193548608B82801, # noqa: E501 + 0x606C4A02EA734CC32ACD2B02BC28B99CB3E287E85A763AF267492AB572E99AB3F370D275CEC1DA1AAA9075FF05F79BE, # noqa: E501 + ), + ) + # point at infinity in G2 + INF_G2 = PointG2((0, 0), (0, 0)) + + # Other test constants + # point not in subgroup in curve Fp + P1_NOT_IN_SUBGROUP = PointG1(0, 2) + P1_NOT_IN_SUBGROUP_TIMES_2 = PointG1(0, P - 2) + # point not in subgroup in curve Fp2 + P2_NOT_IN_SUBGROUP = PointG2( + (1, 1), + ( + 0x17FAA6201231304F270B858DAD9462089F2A5B83388E4B10773ABC1EEF6D193B9FCE4E8EA2D9D28E3C3A315AA7DE14CA, # noqa: E501 + 0xCC12449BE6AC4E7F367E7242250427C4FB4C39325D3164AD397C1837A90F0EA1A534757DF374DD6569345EB41ED76E, # noqa: E501 + ), + ) + P2_NOT_IN_SUBGROUP_TIMES_2 = PointG2( + ( + 0x919F97860ECC3E933E3477FCAC0E2E4FCC35A6E886E935C97511685232456263DEF6665F143CCCCB44C733333331553, # noqa: E501 + 0x18B4376B50398178FA8D78ED2654B0FFD2A487BE4DBE6B69086E61B283F4E9D58389CCCB8EDC99995718A66666661555, # noqa: E501 + ), + ( + 0x26898F699C4B07A405AB4183A10B47F923D1C0FDA1018682DD2CCC88968C1B90D44534D6B9270CF57F8DC6D4891678A, # noqa: E501 + 0x3270414330EAD5EC92219A03A24DFA059DBCBE610868BE1851CC13DAC447F60B40D41113FD007D3307B19ADD4B0F061, # noqa: E501 + ), + ) + + # Pairing precompile results + PAIRING_TRUE = int.to_bytes(1, length=32, byteorder="big") + PAIRING_FALSE = int.to_bytes(0, length=32, byteorder="big") + + # Returned on precompile failure + INVALID = b"" + + +def msm_discount(k: int) -> int: + """ + Returns the discount for the G1MSM and G2MSM precompiles. + """ + return Spec.MSM_DISCOUNT_TABLE[min(k, 128)] + + +def msm_gas_func_gen(len_per_pair: int, multiplication_cost: int) -> Callable[[int], int]: + """ + Generates a function that calculates the gas cost for the G1MSM and G2MSM + precompiles. + """ + + def msm_gas(input_length: int) -> int: + """ + Calculates the gas cost for the G1MSM and G2MSM precompiles. + """ + k = input_length // len_per_pair + if k == 0: + return 0 + + gas_cost = k * multiplication_cost * msm_discount(k) // Spec.MSM_MULTIPLIER + + return gas_cost + + return msm_gas + + +def pairing_gas(input_length: int) -> int: + """ + Calculates the gas cost for the PAIRING precompile. + """ + k = input_length // Spec.LEN_PER_PAIR + return (Spec.PAIRING_PER_PAIR_GAS * k) + Spec.PAIRING_BASE_GAS + + +GAS_CALCULATION_FUNCTION_MAP = { + Spec.G1ADD: lambda _: Spec.G1ADD_GAS, + Spec.G1MUL: lambda _: Spec.G1MUL_GAS, + Spec.G1MSM: msm_gas_func_gen(len(PointG1() + Scalar()), Spec.G1MUL_GAS), + Spec.G2ADD: lambda _: Spec.G2ADD_GAS, + Spec.G2MUL: lambda _: Spec.G2MUL_GAS, + Spec.G2MSM: msm_gas_func_gen(len(PointG2() + Scalar()), Spec.G2MUL_GAS), + Spec.PAIRING: pairing_gas, + Spec.MAP_FP_TO_G1: lambda _: Spec.MAP_FP_TO_G1_GAS, + Spec.MAP_FP2_TO_G2: lambda _: Spec.MAP_FP2_TO_G2_GAS, +} diff --git a/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1add.py b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1add.py new file mode 100644 index 0000000000..7d03436eb8 --- /dev/null +++ b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1add.py @@ -0,0 +1,236 @@ +""" +abstract: Tests BLS12_G1ADD precompile of [EIP-2537: Precompile for BLS12-381 curve operations](https://eips.ethereum.org/EIPS/eip-2537) + Tests BLS12_G1ADD precompile of [EIP-2537: Precompile for BLS12-381 curve operations](https://eips.ethereum.org/EIPS/eip-2537). +""" # noqa: E501 + +import pytest + +from ethereum_test_tools import Environment +from ethereum_test_tools import Opcodes as Op +from ethereum_test_tools import StateTestFiller, Transaction + +from .helpers import vectors_from_file +from .spec import PointG1, Spec, ref_spec_2537 + +REFERENCE_SPEC_GIT_PATH = ref_spec_2537.git_path +REFERENCE_SPEC_VERSION = ref_spec_2537.version + +pytestmark = [ + pytest.mark.valid_from("Prague"), + pytest.mark.parametrize("precompile_address", [Spec.G1ADD], ids=[""]), +] + + +@pytest.mark.parametrize( + "input,expected_output", + vectors_from_file("add_G1_bls.json") + + [ + pytest.param( + Spec.INF_G1 + Spec.INF_G1, + Spec.INF_G1, + id="inf_plus_inf", + ), + pytest.param( + Spec.P1_NOT_IN_SUBGROUP + Spec.P1_NOT_IN_SUBGROUP, + Spec.P1_NOT_IN_SUBGROUP_TIMES_2, + id="not_in_subgroup_1", + ), + pytest.param( + Spec.P1_NOT_IN_SUBGROUP + Spec.P1_NOT_IN_SUBGROUP_TIMES_2, + Spec.INF_G1, + id="not_in_subgroup_2", + ), + ], +) +def test_valid( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Test the BLS12_G1ADD precompile. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) + + +@pytest.mark.parametrize( + "input", + vectors_from_file("fail-add_G1_bls.json") + + [ + pytest.param( + PointG1(0, 1) + Spec.INF_G1, + id="invalid_point_a_1", + ), + pytest.param( + PointG1(Spec.P1.x, Spec.P1.y - 1) + Spec.INF_G1, + id="invalid_point_a_2", + ), + pytest.param( + PointG1(Spec.P1.x, Spec.P1.y + 1) + Spec.INF_G1, + id="invalid_point_a_3", + ), + pytest.param( + PointG1(Spec.P1.x, Spec.P1.x) + Spec.INF_G1, + id="invalid_point_a_4", + ), + pytest.param( + PointG1(Spec.P1.x, Spec.P1.y - 1) + Spec.P1, + id="invalid_point_a_5", + ), + pytest.param( + Spec.INF_G1 + PointG1(0, 1), + id="invalid_point_b_1", + ), + pytest.param( + Spec.INF_G1 + PointG1(Spec.P1.x, Spec.P1.y - 1), + id="invalid_point_b_2", + ), + pytest.param( + Spec.INF_G1 + PointG1(Spec.P1.x, Spec.P1.y + 1), + id="invalid_point_b_3", + ), + pytest.param( + Spec.INF_G1 + PointG1(Spec.P1.x, Spec.P1.x), + id="invalid_point_b_4", + ), + pytest.param( + Spec.P1 + PointG1(Spec.P1.x, Spec.P1.y - 1), + id="invalid_point_b_5", + ), + pytest.param( + PointG1(Spec.P, 0) + Spec.INF_G1, + id="a_x_equal_to_p", + ), + pytest.param( + Spec.INF_G1 + PointG1(Spec.P, 0), + id="b_x_equal_to_p", + ), + pytest.param( + PointG1(0, Spec.P) + Spec.INF_G1, + id="a_y_equal_to_p", + ), + pytest.param( + Spec.INF_G1 + PointG1(0, Spec.P), + id="b_y_equal_to_p", + ), + pytest.param( + b"\x80" + bytes(Spec.INF_G1)[1:] + Spec.INF_G1, + id="invalid_encoding_a", + ), + pytest.param( + Spec.INF_G1 + b"\x80" + bytes(Spec.INF_G1)[1:], + id="invalid_encoding_b", + ), + pytest.param( + (Spec.INF_G1 + PointG1(Spec.P1.x, Spec.P1.x))[:-1], + id="input_too_short", + ), + pytest.param( + b"\x00" + (Spec.INF_G1 + PointG1(Spec.P1.x, Spec.P1.x)), + id="input_too_long", + ), + pytest.param( + b"", + id="zero_length_input", + ), + pytest.param( + Spec.G1, + id="only_one_point", + ), + pytest.param( + Spec.G2 + Spec.G2, + id="g2_points", + ), + ], +) +@pytest.mark.parametrize("expected_output", [Spec.INVALID], ids=[""]) +def test_invalid( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Negative tests for the BLS12_G1ADD precompile. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) + + +@pytest.mark.parametrize( + "input,expected_output,precompile_gas_modifier", + [ + pytest.param( + Spec.INF_G1 + Spec.INF_G1, + Spec.INF_G1, + 1, + id="extra_gas", + ), + pytest.param( + Spec.INF_G1 + Spec.INF_G1, + Spec.INVALID, + -1, + id="insufficient_gas", + ), + ], +) +def test_gas( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Test the BLS12_G1ADD precompile gas requirements. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) + + +@pytest.mark.parametrize( + "call_opcode", + [ + Op.STATICCALL, + Op.DELEGATECALL, + Op.CALLCODE, + ], +) +@pytest.mark.parametrize( + "input,expected_output", + [ + pytest.param( + Spec.INF_G1 + Spec.INF_G1, + Spec.INF_G1, + id="inf_plus_inf", + ), + ], +) +def test_call_types( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Test the BLS12_G1ADD precompile using different call types. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) diff --git a/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1msm.py b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1msm.py new file mode 100644 index 0000000000..94f53e45dd --- /dev/null +++ b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1msm.py @@ -0,0 +1,153 @@ +""" +abstract: Tests BLS12_G1MSM precompile of [EIP-2537: Precompile for BLS12-381 curve operations](https://eips.ethereum.org/EIPS/eip-2537) + Tests BLS12_G1MSM precompile of [EIP-2537: Precompile for BLS12-381 curve operations](https://eips.ethereum.org/EIPS/eip-2537). +""" # noqa: E501 + +import pytest + +from ethereum_test_tools import Environment +from ethereum_test_tools import Opcodes as Op +from ethereum_test_tools import StateTestFiller, Transaction + +from .helpers import vectors_from_file +from .spec import PointG1, Scalar, Spec, ref_spec_2537 + +REFERENCE_SPEC_GIT_PATH = ref_spec_2537.git_path +REFERENCE_SPEC_VERSION = ref_spec_2537.version + +pytestmark = [ + pytest.mark.valid_from("Prague"), + pytest.mark.parametrize("precompile_address", [Spec.G1MSM], ids=[""]), +] + + +@pytest.mark.parametrize( + "input,expected_output", + vectors_from_file("multiexp_G1_bls.json") + + [ + pytest.param( + (Spec.P1 + Scalar(Spec.Q)) * (len(Spec.MSM_DISCOUNT_TABLE) - 1), + Spec.INF_G1, + id="max_discount", + ), + pytest.param( + (Spec.P1 + Scalar(Spec.Q)) * len(Spec.MSM_DISCOUNT_TABLE), + Spec.INF_G1, + id="max_discount_plus_1", + ), + ], +) +def test_valid( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Test the BLS12_G1MSM precompile. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) + + +@pytest.mark.parametrize( + "input", + vectors_from_file("fail-multiexp_G1_bls.json") + + [ + pytest.param( + PointG1(0, 1) + Scalar(0), + id="invalid_point_1", + ), + pytest.param( + PointG1(Spec.P1.x, Spec.P1.y - 1) + Scalar(0), + id="invalid_point_2", + ), + pytest.param( + PointG1(Spec.P1.x, Spec.P1.y + 1) + Scalar(0), + id="invalid_point_3", + ), + pytest.param( + PointG1(Spec.P1.x, Spec.P1.x) + Scalar(0), + id="invalid_point_4", + ), + pytest.param( + b"\x80" + bytes(Spec.INF_G1)[1:] + Scalar(0), + id="invalid_encoding", + ), + pytest.param( + b"\x80" + bytes(Spec.INF_G1)[1:] + Scalar(0), + id="invalid_encoding", + ), + pytest.param( + Spec.P1_NOT_IN_SUBGROUP + Scalar(Spec.Q), + id="not_in_subgroup_1", + ), + pytest.param( + Spec.P1_NOT_IN_SUBGROUP_TIMES_2 + Scalar(Spec.Q), + id="not_in_subgroup_2", + ), + pytest.param( + Spec.G1, + id="bls_g1_truncated_input", + ), + ], + # Input length tests can be found in ./test_bls12_variable_length_input_contracts.py +) +@pytest.mark.parametrize( + "precompile_gas_modifier", [100_000], ids=[""] +) # Add gas so that won't be the cause of failure +@pytest.mark.parametrize("expected_output", [Spec.INVALID], ids=[""]) +def test_invalid( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Test the BLS12_G1MSM precompile. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) + + +@pytest.mark.parametrize( + "call_opcode", + [ + Op.STATICCALL, + Op.DELEGATECALL, + Op.CALLCODE, + ], +) +@pytest.mark.parametrize( + "input,expected_output", + [ + pytest.param( + Spec.INF_G1 + Scalar(0), + Spec.INF_G1, + id="inf_times_zero", + ), + ], +) +def test_call_types( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Test the BLS12_G1MSM precompile using different call types. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) diff --git a/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1mul.py b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1mul.py new file mode 100644 index 0000000000..9b2c24232e --- /dev/null +++ b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1mul.py @@ -0,0 +1,240 @@ +""" +abstract: Tests BLS12_G1MUL precompile of [EIP-2537: Precompile for BLS12-381 curve operations](https://eips.ethereum.org/EIPS/eip-2537) + Tests BLS12_G1MUL precompile of [EIP-2537: Precompile for BLS12-381 curve operations](https://eips.ethereum.org/EIPS/eip-2537). +""" # noqa: E501 + +import pytest + +from ethereum_test_tools import Environment +from ethereum_test_tools import Opcodes as Op +from ethereum_test_tools import StateTestFiller, Transaction + +from .helpers import vectors_from_file +from .spec import PointG1, Scalar, Spec, ref_spec_2537 + +REFERENCE_SPEC_GIT_PATH = ref_spec_2537.git_path +REFERENCE_SPEC_VERSION = ref_spec_2537.version + +pytestmark = [ + pytest.mark.valid_from("Prague"), + pytest.mark.parametrize("precompile_address", [Spec.G1MUL], ids=[""]), +] + + +@pytest.mark.parametrize( + "input,expected_output", + vectors_from_file("mul_G1_bls.json") + + [ + pytest.param( + Spec.INF_G1 + Scalar(0), + Spec.INF_G1, + id="bls_g1mul_(0*inf=inf)", + ), + pytest.param( + Spec.INF_G1 + Scalar(2**256 - 1), + Spec.INF_G1, + id="bls_g1mul_(2**256-1*inf=inf)", + ), + pytest.param( + Spec.P1 + Scalar(2**256 - 1), + PointG1( + 0x3DA1F13DDEF2B8B5A46CD543CE56C0A90B8B3B0D6D43DEC95836A5FD2BACD6AA8F692601F870CF22E05DDA5E83F460B, # noqa: E501 + 0x18D64F3C0E9785365CBDB375795454A8A4FA26F30B9C4F6E33CA078EB5C29B7AEA478B076C619BC1ED22B14C95569B2D, # noqa: E501 + ), + id="bls_g1mul_(2**256-1*P1)", + ), + pytest.param( + Spec.P1 + Scalar(Spec.Q - 1), + -Spec.P1, # negated P1 + id="bls_g1mul_(q-1*P1)", + ), + pytest.param( + Spec.P1 + Scalar(Spec.Q), + Spec.INF_G1, + id="bls_g1mul_(q*P1)", + ), + pytest.param( + Spec.P1 + Scalar(Spec.Q + 1), + Spec.P1, + id="bls_g1mul_(q+1*P1)", + ), + pytest.param( + Spec.P1 + Scalar(2 * Spec.Q), + Spec.INF_G1, + id="bls_g1mul_(2q*P1)", + ), + pytest.param( + Spec.P1 + Scalar((2**256 // Spec.Q) * Spec.Q), + Spec.INF_G1, + id="bls_g1mul_(Nq*P1)", + ), + ], +) +def test_valid( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Test the BLS12_G1MUL precompile. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) + + +@pytest.mark.parametrize( + "input", + vectors_from_file("fail-mul_G1_bls.json") + + [ + pytest.param( + PointG1(0, 1) + Scalar(0), + id="invalid_point_1", + ), + pytest.param( + PointG1(Spec.P1.x, Spec.P1.y - 1) + Scalar(0), + id="invalid_point_2", + ), + pytest.param( + PointG1(Spec.P1.x, Spec.P1.y + 1) + Scalar(0), + id="invalid_point_3", + ), + pytest.param( + PointG1(Spec.P1.x, Spec.P1.x) + Scalar(0), + id="invalid_point_4", + ), + pytest.param( + b"\x80" + bytes(Spec.INF_G1)[1:] + Scalar(0), + id="invalid_encoding", + ), + pytest.param( + (Spec.INF_G1 + Scalar(0))[:-1], + id="input_too_short", + ), + pytest.param( + b"\x00" + (Spec.INF_G1 + Scalar(0)), + id="input_too_long", + ), + pytest.param( + b"", + id="zero_length_input", + ), + pytest.param( + Spec.P1_NOT_IN_SUBGROUP + Scalar(1), + id="bls_g1mul_not_in_subgroup_1", + ), + pytest.param( + Spec.P1_NOT_IN_SUBGROUP_TIMES_2 + Scalar(1), + id="bls_g1mul_not_in_subgroup_2", + ), + pytest.param( + Spec.P1_NOT_IN_SUBGROUP_TIMES_2 + Scalar(Spec.Q), + id="bls_g1mul_not_in_subgroup_times_q", + ), + pytest.param( + Spec.P1_NOT_IN_SUBGROUP + Scalar(Spec.Q), + id="bls_g1mul_not_in_subgroup_times_q_2", + ), + pytest.param( + Spec.G1 + Spec.G1, + id="bls_g1_add_input_invalid_length", + ), + pytest.param( + Spec.G2 + Spec.G2, + id="bls_g2_add_input_invalid_length", + ), + pytest.param( + Spec.G1, + id="bls_g1_truncated_input", + ), + ], +) +@pytest.mark.parametrize("expected_output", [Spec.INVALID], ids=[""]) +def test_invalid( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Negative tests for the BLS12_G1MUL precompile. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) + + +@pytest.mark.parametrize( + "input,expected_output,precompile_gas_modifier", + [ + pytest.param( + Spec.INF_G1 + Scalar(0), + Spec.INF_G1, + 1, + id="extra_gas", + ), + pytest.param( + Spec.INF_G1 + Scalar(0), + Spec.INVALID, + -1, + id="insufficient_gas", + ), + ], +) +def test_gas( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Test the BLS12_G1MUL precompile gas requirements. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) + + +@pytest.mark.parametrize( + "call_opcode", + [ + Op.STATICCALL, + Op.DELEGATECALL, + Op.CALLCODE, + ], +) +@pytest.mark.parametrize( + "input,expected_output", + [ + pytest.param( + Spec.INF_G1 + Scalar(0), + Spec.INF_G1, + id="bls_g1mul_(0*inf=inf)", + ), + ], +) +def test_call_types( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Test the BLS12_G1MUL precompile using different call types. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) diff --git a/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g2add.py b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g2add.py new file mode 100644 index 0000000000..adcd905427 --- /dev/null +++ b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g2add.py @@ -0,0 +1,242 @@ +""" +abstract: Tests BLS12_G2ADD precompile of [EIP-2537: Precompile for BLS12-381 curve operations](https://eips.ethereum.org/EIPS/eip-2537) + Tests BLS12_G2ADD precompile of [EIP-2537: Precompile for BLS12-381 curve operations](https://eips.ethereum.org/EIPS/eip-2537). +""" # noqa: E501 + +import pytest + +from ethereum_test_tools import Environment +from ethereum_test_tools import Opcodes as Op +from ethereum_test_tools import StateTestFiller, Transaction + +from .helpers import vectors_from_file +from .spec import PointG2, Spec, ref_spec_2537 + +REFERENCE_SPEC_GIT_PATH = ref_spec_2537.git_path +REFERENCE_SPEC_VERSION = ref_spec_2537.version + +pytestmark = [ + pytest.mark.valid_from("Prague"), + pytest.mark.parametrize("precompile_address", [Spec.G2ADD], ids=[""]), +] + + +@pytest.mark.parametrize( + "input,expected_output", + vectors_from_file("add_G2_bls.json") + + [ + pytest.param( + Spec.P2_NOT_IN_SUBGROUP + Spec.P2_NOT_IN_SUBGROUP, + Spec.P2_NOT_IN_SUBGROUP_TIMES_2, + id="not_in_subgroup", + ), + ], +) +def test_valid( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Test the BLS12_G2ADD precompile. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) + + +@pytest.mark.parametrize( + "input", + vectors_from_file("fail-add_G2_bls.json") + + [ + pytest.param( + PointG2((1, 0), (0, 0)) + Spec.INF_G2, + id="invalid_point_a_1", + ), + pytest.param( + PointG2((0, 0), (1, 0)) + Spec.INF_G2, + id="invalid_point_a_2", + ), + pytest.param( + PointG2((0, 1), (0, 0)) + Spec.INF_G2, + id="invalid_point_a_3", + ), + pytest.param( + PointG2((0, 0), (0, 1)) + Spec.INF_G2, + id="invalid_point_a_4", + ), + pytest.param( + PointG2(Spec.P2.x, (Spec.P2.y[0], Spec.P2.y[1] - 1)) + Spec.P2, + id="invalid_point_a_5", + ), + pytest.param( + Spec.INF_G2 + PointG2((1, 0), (0, 0)), + id="invalid_point_b_1", + ), + pytest.param( + Spec.INF_G2 + PointG2((0, 0), (1, 0)), + id="invalid_point_b_2", + ), + pytest.param( + Spec.INF_G2 + PointG2((0, 1), (0, 0)), + id="invalid_point_b_3", + ), + pytest.param( + Spec.INF_G2 + PointG2((0, 0), (0, 1)), + id="invalid_point_b_4", + ), + pytest.param( + Spec.P2 + PointG2(Spec.P2.x, (Spec.P2.y[0], Spec.P2.y[1] - 1)), + id="invalid_point_b_5", + ), + pytest.param( + PointG2((Spec.P, 0), (0, 0)) + Spec.INF_G2, + id="a_x_1_equal_to_p", + ), + pytest.param( + PointG2((0, Spec.P), (0, 0)) + Spec.INF_G2, + id="a_x_2_equal_to_p", + ), + pytest.param( + PointG2((0, 0), (Spec.P, 0)) + Spec.INF_G2, + id="a_y_1_equal_to_p", + ), + pytest.param( + PointG2((0, 0), (0, Spec.P)) + Spec.INF_G2, + id="a_y_2_equal_to_p", + ), + pytest.param( + Spec.INF_G2 + PointG2((Spec.P, 0), (0, 0)), + id="b_x_1_equal_to_p", + ), + pytest.param( + Spec.INF_G2 + PointG2((0, Spec.P), (0, 0)), + id="b_x_2_equal_to_p", + ), + pytest.param( + Spec.INF_G2 + PointG2((0, 0), (Spec.P, 0)), + id="b_y_1_equal_to_p", + ), + pytest.param( + Spec.INF_G2 + PointG2((0, 0), (0, Spec.P)), + id="b_y_2_equal_to_p", + ), + pytest.param( + b"\x80" + bytes(Spec.INF_G2)[1:] + Spec.INF_G2, + id="invalid_encoding_a", + ), + pytest.param( + Spec.INF_G2 + b"\x80" + bytes(Spec.INF_G2)[1:], + id="invalid_encoding_b", + ), + pytest.param( + (Spec.INF_G2 + Spec.INF_G2)[:-1], + id="input_too_short", + ), + pytest.param( + b"\x00" + (Spec.INF_G2 + Spec.INF_G2), + id="input_too_long", + ), + pytest.param( + b"", + id="zero_length_input", + ), + pytest.param( + Spec.G2, + id="only_one_point", + ), + pytest.param( + Spec.G1 + Spec.G1, + id="g1_points", + ), + ], +) +@pytest.mark.parametrize("expected_output", [Spec.INVALID], ids=[""]) +def test_invalid( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Negative tests for the BLS12_G2ADD precompile. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) + + +@pytest.mark.parametrize( + "input,expected_output,precompile_gas_modifier", + [ + pytest.param( + Spec.INF_G2 + Spec.INF_G2, + Spec.INF_G2, + 1, + id="extra_gas", + ), + pytest.param( + Spec.INF_G2 + Spec.INF_G2, + Spec.INVALID, + -1, + id="insufficient_gas", + ), + ], +) +def test_gas( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Test the BLS12_G2ADD precompile gas requirements. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) + + +@pytest.mark.parametrize( + "call_opcode", + [ + Op.STATICCALL, + Op.DELEGATECALL, + Op.CALLCODE, + ], +) +@pytest.mark.parametrize( + "input,expected_output", + [ + pytest.param( + Spec.INF_G2 + Spec.INF_G2, + Spec.INF_G2, + id="inf_plus_inf", + ), + ], +) +def test_call_types( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Test the BLS12_G2ADD precompile using different call types. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) diff --git a/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g2msm.py b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g2msm.py new file mode 100644 index 0000000000..e3236c7787 --- /dev/null +++ b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g2msm.py @@ -0,0 +1,143 @@ +""" +abstract: Tests BLS12_G2MSM precompile of [EIP-2537: Precompile for BLS12-381 curve operations](https://eips.ethereum.org/EIPS/eip-2537) + Tests BLS12_G2MSM precompile of [EIP-2537: Precompile for BLS12-381 curve operations](https://eips.ethereum.org/EIPS/eip-2537). +""" # noqa: E501 + +import pytest + +from ethereum_test_tools import Environment +from ethereum_test_tools import Opcodes as Op +from ethereum_test_tools import StateTestFiller, Transaction + +from .helpers import vectors_from_file +from .spec import PointG2, Scalar, Spec, ref_spec_2537 + +REFERENCE_SPEC_GIT_PATH = ref_spec_2537.git_path +REFERENCE_SPEC_VERSION = ref_spec_2537.version + +pytestmark = [ + pytest.mark.valid_from("Prague"), + pytest.mark.parametrize("precompile_address", [Spec.G2MSM], ids=[""]), +] + + +@pytest.mark.parametrize("input,expected_output", vectors_from_file("multiexp_G2_bls.json")) +def test_valid( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Test the BLS12_G2MSM precompile. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) + + +@pytest.mark.parametrize( + "input", + vectors_from_file("fail-multiexp_G2_bls.json") + + [ + pytest.param( + PointG2((1, 0), (0, 0)) + Scalar(0), + id="invalid_point_a_1", + ), + pytest.param( + PointG2((0, 1), (0, 0)) + Scalar(0), + id="invalid_point_a_2", + ), + pytest.param( + PointG2((0, 0), (1, 0)) + Scalar(0), + id="invalid_point_a_3", + ), + pytest.param( + PointG2((0, 0), (0, 1)) + Scalar(0), + id="invalid_point_a_4", + ), + pytest.param( + PointG2((Spec.P, 0), (0, 0)) + Scalar(0), + id="x_1_equal_to_p", + ), + pytest.param( + PointG2((0, Spec.P), (0, 0)) + Scalar(0), + id="x_2_equal_to_p", + ), + pytest.param( + PointG2((0, 0), (Spec.P, 0)) + Scalar(0), + id="y_1_equal_to_p", + ), + pytest.param( + PointG2((0, 0), (0, Spec.P)) + Scalar(0), + id="y_2_equal_to_p", + ), + pytest.param( + b"\x80" + bytes(Spec.INF_G2)[1:] + Scalar(0), + id="invalid_encoding", + ), + pytest.param( + Spec.P2_NOT_IN_SUBGROUP + Scalar(1), + id="bls_g2mul_not_in_subgroup", + ), + pytest.param( + Spec.G2, + id="bls_g2_truncated_input", + ), + # Input length tests can be found in ./test_bls12_variable_length_input_contracts.py + ], +) +@pytest.mark.parametrize("expected_output", [Spec.INVALID], ids=[""]) +def test_invalid( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Negative tests for the BLS12_G2MSM precompile. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) + + +@pytest.mark.parametrize( + "call_opcode", + [ + Op.STATICCALL, + Op.DELEGATECALL, + Op.CALLCODE, + ], +) +@pytest.mark.parametrize( + "input,expected_output", + [ + pytest.param( + Spec.INF_G2 + Scalar(0), + Spec.INF_G2, + id="inf_times_zero", + ), + ], +) +def test_call_types( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Test the BLS12_G2MSM precompile using different call types. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) diff --git a/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g2mul.py b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g2mul.py new file mode 100644 index 0000000000..118a4449fe --- /dev/null +++ b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g2mul.py @@ -0,0 +1,263 @@ +""" +abstract: Tests BLS12_G2MUL precompile of [EIP-2537: Precompile for BLS12-381 curve operations](https://eips.ethereum.org/EIPS/eip-2537) + Tests BLS12_G2MUL precompile of [EIP-2537: Precompile for BLS12-381 curve operations](https://eips.ethereum.org/EIPS/eip-2537). +""" # noqa: E501 + +import pytest + +from ethereum_test_tools import Environment +from ethereum_test_tools import Opcodes as Op +from ethereum_test_tools import StateTestFiller, Transaction + +from .helpers import vectors_from_file +from .spec import PointG2, Scalar, Spec, ref_spec_2537 + +REFERENCE_SPEC_GIT_PATH = ref_spec_2537.git_path +REFERENCE_SPEC_VERSION = ref_spec_2537.version + +pytestmark = [ + pytest.mark.valid_from("Prague"), + pytest.mark.parametrize("precompile_address", [Spec.G2MUL], ids=[""]), +] + + +@pytest.mark.parametrize( + "input,expected_output", + vectors_from_file("mul_G2_bls.json") + + [ + pytest.param( + Spec.INF_G2 + Scalar(0), + Spec.INF_G2, + id="bls_g2mul_(0*inf=inf)", + ), + pytest.param( + Spec.INF_G2 + Scalar(2**256 - 1), + Spec.INF_G2, + id="bls_g2mul_(2**256-1*inf=inf)", + ), + pytest.param( + Spec.P2 + Scalar(2**256 - 1), + PointG2( + ( + 0x2663E1C3431E174CA80E5A84489569462E13B52DA27E7720AF5567941603475F1F9BC0102E13B92A0A21D96B94E9B22, # noqa: E501 + 0x6A80D056486365020A6B53E2680B2D72D8A93561FC2F72B960936BB16F509C1A39C4E4174A7C9219E3D7EF130317C05, # noqa: E501 + ), + ( + 0xC49EAD39E9EB7E36E8BC25824299661D5B6D0E200BBC527ECCB946134726BF5DBD861E8E6EC946260B82ED26AFE15FB, # noqa: E501 + 0x5397DAD1357CF8333189821B737172B18099ECF7EE8BDB4B3F05EBCCDF40E1782A6C71436D5ACE0843D7F361CBC6DB2, # noqa: E501 + ), + ), + id="bls_g2mul_(2**256-1*P2)", + ), + pytest.param( + Spec.P2 + Scalar(Spec.Q - 1), + -Spec.P2, # negated P2 + id="bls_g2mul_(q-1*P2)", + ), + pytest.param( + Spec.P2 + Scalar(Spec.Q), + Spec.INF_G2, + id="bls_g2mul_(q*P2)", + ), + pytest.param( + Spec.G2 + Scalar(Spec.Q), + Spec.INF_G2, + id="bls_g2mul_(q*G2)", + ), + pytest.param( + Spec.P2 + Scalar(Spec.Q + 1), + Spec.P2, + id="bls_g2mul_(q+1*P2)", + ), + pytest.param( + Spec.P2 + Scalar(2 * Spec.Q), + Spec.INF_G2, + id="bls_g2mul_(2q*P2)", + ), + pytest.param( + Spec.P2 + Scalar((2**256 // Spec.Q) * Spec.Q), + Spec.INF_G2, + id="bls_g2mul_(Nq*P2)", + ), + ], +) +def test_valid( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Test the BLS12_G2MUL precompile. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) + + +@pytest.mark.parametrize( + "input", + vectors_from_file("fail-mul_G2_bls.json") + + [ + pytest.param( + PointG2((1, 0), (0, 0)) + Scalar(0), + id="invalid_point_a_1", + ), + pytest.param( + PointG2((0, 1), (0, 0)) + Scalar(0), + id="invalid_point_a_2", + ), + pytest.param( + PointG2((0, 0), (1, 0)) + Scalar(0), + id="invalid_point_a_3", + ), + pytest.param( + PointG2((0, 0), (0, 1)) + Scalar(0), + id="invalid_point_a_4", + ), + pytest.param( + PointG2((Spec.P, 0), (0, 0)) + Scalar(0), + id="x_1_equal_to_p", + ), + pytest.param( + PointG2((0, Spec.P), (0, 0)) + Scalar(0), + id="x_2_equal_to_p", + ), + pytest.param( + PointG2((0, 0), (Spec.P, 0)) + Scalar(0), + id="y_1_equal_to_p", + ), + pytest.param( + PointG2((0, 0), (0, Spec.P)) + Scalar(0), + id="y_2_equal_to_p", + ), + pytest.param( + b"\x80" + bytes(Spec.INF_G2)[1:] + Scalar(0), + id="invalid_encoding", + ), + pytest.param( + (Spec.INF_G2 + Scalar(0))[:-1], + id="input_too_short", + ), + pytest.param( + b"\x00" + (Spec.INF_G2 + Scalar(0)), + id="input_too_long", + ), + pytest.param( + b"", + id="zero_length_input", + ), + pytest.param( + Spec.P2_NOT_IN_SUBGROUP + Scalar(1), + id="bls_g2mul_not_in_subgroup", + ), + pytest.param( + Spec.P2_NOT_IN_SUBGROUP + Scalar(2), + id="bls_g2mul_not_in_subgroup_times_2", + ), + pytest.param( + Spec.P2_NOT_IN_SUBGROUP + Scalar(Spec.Q), + id="bls_g2mul_not_in_subgroup_times_q", + ), + pytest.param( + Spec.G1 + Spec.G1, + id="bls_g1_add_input_invalid_length", + ), + pytest.param( + Spec.G2 + Spec.G2, + id="bls_g2_add_input_invalid_length", + ), + pytest.param( + Spec.G2, + id="bls_g2_truncated_input", + ), + ], +) +@pytest.mark.parametrize("expected_output", [Spec.INVALID], ids=[""]) +def test_invalid( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Negative tests for the BLS12_G2MUL precompile. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) + + +@pytest.mark.parametrize( + "input,expected_output,precompile_gas_modifier", + [ + pytest.param( + Spec.INF_G2 + Scalar(0), + Spec.INF_G2, + 1, + id="extra_gas", + ), + pytest.param( + Spec.INF_G2 + Scalar(0), + Spec.INVALID, + -1, + id="insufficient_gas", + ), + ], +) +def test_gas( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Test the BLS12_G1MUL precompile gas requirements. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) + + +@pytest.mark.parametrize( + "call_opcode", + [ + Op.STATICCALL, + Op.DELEGATECALL, + Op.CALLCODE, + ], +) +@pytest.mark.parametrize( + "input,expected_output", + [ + pytest.param( + Spec.INF_G2 + Scalar(0), + Spec.INF_G2, + id="bls_g2mul_(0*inf=inf)", + ), + ], +) +def test_call_types( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Test the BLS12_G2MUL using different call types. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) diff --git a/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_map_fp2_to_g2.py b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_map_fp2_to_g2.py new file mode 100644 index 0000000000..385f3ad38f --- /dev/null +++ b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_map_fp2_to_g2.py @@ -0,0 +1,177 @@ +""" +abstract: Tests BLS12_MAP_FP2_TO_G2 precompile of [EIP-2537: Precompile for BLS12-381 curve operations](https://eips.ethereum.org/EIPS/eip-2537) + Tests BLS12_MAP_FP2_TO_G2 precompile of [EIP-2537: Precompile for BLS12-381 curve operations](https://eips.ethereum.org/EIPS/eip-2537). +""" # noqa: E501 + + +import pytest + +from ethereum_test_tools import Environment +from ethereum_test_tools import Opcodes as Op +from ethereum_test_tools import StateTestFiller, Transaction + +from .helpers import vectors_from_file +from .spec import FP2, PointG2, Spec, ref_spec_2537 + +REFERENCE_SPEC_GIT_PATH = ref_spec_2537.git_path +REFERENCE_SPEC_VERSION = ref_spec_2537.version + +pytestmark = [ + pytest.mark.valid_from("Prague"), + pytest.mark.parametrize("precompile_address", [Spec.MAP_FP2_TO_G2], ids=[""]), +] + +G2_POINT_ZERO_FP = PointG2( + ( + 0x18320896EC9EEF9D5E619848DC29CE266F413D02DD31D9B9D44EC0C79CD61F18B075DDBA6D7BD20B7FF27A4B324BFCE, # noqa: E501 + 0xA67D12118B5A35BB02D2E86B3EBFA7E23410DB93DE39FB06D7025FA95E96FFA428A7A27C3AE4DD4B40BD251AC658892, # noqa: E501 + ), + ( + 0x260E03644D1A2C321256B3246BAD2B895CAD13890CBE6F85DF55106A0D334604FB143C7A042D878006271865BC35941, # noqa: E501 + 0x4C69777A43F0BDA07679D5805E63F18CF4E0E7C6112AC7F70266D199B4F76AE27C6269A3CEEBDAE30806E9A76AADF5C, # noqa: E501 + ), +) + + +@pytest.mark.parametrize( + "input,expected_output", + vectors_from_file("map_fp2_to_G2_bls.json") + + [ + pytest.param( + FP2((0, 0)), + G2_POINT_ZERO_FP, + id="fp_0", + ), + pytest.param( + FP2((Spec.P - 1, Spec.P - 1)), + PointG2( + ( + 0x9BF1B857D8C15F317F649ACCFA7023EF21CFC03059936B83B487DB476FF9D2FE64C6147140A5F0A436B875F51FFDF07, # noqa: E501 + 0xBB10E09BDF236CB2951BD7BCC044E1B9A6BB5FD4B2019DCC20FFDE851D52D4F0D1A32382AF9D7DA2C5BA27E0F1C69E6, # noqa: E501 + ), + ( + 0xDD416A927AB1C15490AB753C973FD377387B12EFCBE6BED2BF768B9DC95A0CA04D1A8F0F30DBC078A2350A1F823CFD3, # noqa: E501 + 0x171565CE4FCD047B35EA6BCEE4EF6FDBFEC8CC73B7ACDB3A1EC97A776E13ACDFEFFC21ED6648E3F0EEC53DDB6C20FB61, # noqa: E501 + ), + ), + id="fp_p_minus_1", + ), + ], +) +def test_valid( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Test the BLS12_MAP_FP2_TO_G2 precompile. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) + + +@pytest.mark.parametrize( + "input", + vectors_from_file("fail-map_fp2_to_G2_bls.json") + + [ + pytest.param(b"\x80" + bytes(FP2((0, 0)))[1:], id="invalid_encoding"), + pytest.param(bytes(FP2((0, 0)))[1:], id="input_too_short"), + pytest.param(b"\x00" + FP2((0, 0)), id="input_too_long"), + pytest.param(b"", id="zero_length_input"), + pytest.param(FP2((Spec.P, 0)), id="fq_eq_q"), + pytest.param(FP2((0, Spec.P)), id="fq_eq_q_2"), + pytest.param(FP2((2**512 - 1, 0)), id="fq_eq_2_512_minus_1"), + pytest.param(FP2((0, 2**512 - 1)), id="fq_eq_2_512_minus_1_2"), + pytest.param(Spec.G2, id="g2_input"), + ], +) +@pytest.mark.parametrize("expected_output", [Spec.INVALID], ids=[""]) +def test_invalid( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Negative tests for the BLS12_MAP_FP_TO_G2 precompile. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) + + +@pytest.mark.parametrize( + "input,expected_output,precompile_gas_modifier", + [ + pytest.param( + FP2((0, 0)), + G2_POINT_ZERO_FP, + 1, + id="extra_gas", + ), + pytest.param( + FP2((0, 0)), + Spec.INVALID, + -1, + id="insufficient_gas", + ), + ], +) +def test_gas( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Test the BLS12_MAP_FP_TO_G2 precompile gas requirements. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) + + +@pytest.mark.parametrize( + "call_opcode", + [ + Op.STATICCALL, + Op.DELEGATECALL, + Op.CALLCODE, + ], +) +@pytest.mark.parametrize( + "input,expected_output", + [ + pytest.param( + FP2((0, 0)), + G2_POINT_ZERO_FP, + id="fp_0", + ), + ], +) +def test_call_types( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Test the BLS12_MAP_FP_TO_G2 precompile using different call types. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) diff --git a/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_map_fp_to_g1.py b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_map_fp_to_g1.py new file mode 100644 index 0000000000..71247fcb49 --- /dev/null +++ b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_map_fp_to_g1.py @@ -0,0 +1,163 @@ +""" +abstract: Tests BLS12_MAP_FP_TO_G1 precompile of [EIP-2537: Precompile for BLS12-381 curve operations](https://eips.ethereum.org/EIPS/eip-2537) + Tests BLS12_MAP_FP_TO_G1 precompile of [EIP-2537: Precompile for BLS12-381 curve operations](https://eips.ethereum.org/EIPS/eip-2537). +""" # noqa: E501 + + +import pytest + +from ethereum_test_tools import Environment +from ethereum_test_tools import Opcodes as Op +from ethereum_test_tools import StateTestFiller, Transaction + +from .helpers import vectors_from_file +from .spec import FP, PointG1, Spec, ref_spec_2537 + +REFERENCE_SPEC_GIT_PATH = ref_spec_2537.git_path +REFERENCE_SPEC_VERSION = ref_spec_2537.version + +pytestmark = [ + pytest.mark.valid_from("Prague"), + pytest.mark.parametrize("precompile_address", [Spec.MAP_FP_TO_G1], ids=[""]), +] + +G1_POINT_ZERO_FP = PointG1( + 0x11A9A0372B8F332D5C30DE9AD14E50372A73FA4C45D5F2FA5097F2D6FB93BCAC592F2E1711AC43DB0519870C7D0EA415, # noqa: E501 + 0x92C0F994164A0719F51C24BA3788DE240FF926B55F58C445116E8BC6A47CD63392FD4E8E22BDF9FEAA96EE773222133, # noqa: E501 +) + + +@pytest.mark.parametrize( + "input,expected_output", + vectors_from_file("map_fp_to_G1_bls.json") + + [ + pytest.param( + FP(0), + G1_POINT_ZERO_FP, + id="fp_0", + ), + pytest.param( + FP(Spec.P - 1), + PointG1( + 0x1073311196F8EF19477219CCEE3A48035FF432295AA9419EED45D186027D88B90832E14C4F0E2AA4D15F54D1C3ED0F93, # noqa: E501 + 0x16B3A3B2E3DDDF6A11459DDAF657FDE21C4F10282A56029D9B55AB3CE1F41E1CF39AD27E0EA35823C7D3250E81FF3D66, # noqa: E501 + ), + id="fp_p_minus_1", + ), + ], +) +def test_valid( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Test the BLS12_MAP_FP_TO_G1 precompile. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) + + +@pytest.mark.parametrize( + "input", + vectors_from_file("fail-map_fp_to_G1_bls.json") + + [ + pytest.param(b"\x80" + bytes(FP(0))[1:], id="invalid_encoding"), + pytest.param(bytes(FP(0))[1:], id="input_too_short"), + pytest.param(b"\x00" + FP(0), id="input_too_long"), + pytest.param(b"", id="zero_length_input"), + pytest.param(FP(Spec.P), id="fq_eq_q"), + pytest.param(FP(2**512 - 1), id="fq_eq_2_512_minus_1"), + pytest.param(Spec.G1, id="g1_point_input"), + ], +) +@pytest.mark.parametrize("expected_output", [Spec.INVALID], ids=[""]) +def test_invalid( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Negative tests for the BLS12_MAP_FP_TO_G1 precompile. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) + + +@pytest.mark.parametrize( + "input,expected_output,precompile_gas_modifier", + [ + pytest.param( + FP(0), + G1_POINT_ZERO_FP, + 1, + id="extra_gas", + ), + pytest.param( + FP(0), + Spec.INVALID, + -1, + id="insufficient_gas", + ), + ], +) +def test_gas( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Test the BLS12_MAP_FP_TO_G1 precompile gas requirements. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) + + +@pytest.mark.parametrize( + "call_opcode", + [ + Op.STATICCALL, + Op.DELEGATECALL, + Op.CALLCODE, + ], +) +@pytest.mark.parametrize( + "input,expected_output", + [ + pytest.param( + FP(0), + G1_POINT_ZERO_FP, + id="fp_0", + ), + ], +) +def test_call_types( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Test the BLS12_MAP_FP_TO_G1 precompile using different call types. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) diff --git a/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_pairing.py b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_pairing.py new file mode 100644 index 0000000000..729f685525 --- /dev/null +++ b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_pairing.py @@ -0,0 +1,158 @@ +""" +abstract: Tests BLS12_PAIRING precompile of [EIP-2537: Precompile for BLS12-381 curve operations](https://eips.ethereum.org/EIPS/eip-2537) + Tests BLS12_PAIRING precompile of [EIP-2537: Precompile for BLS12-381 curve operations](https://eips.ethereum.org/EIPS/eip-2537). +""" # noqa: E501 + +import pytest + +from ethereum_test_tools import Environment +from ethereum_test_tools import Opcodes as Op +from ethereum_test_tools import StateTestFiller, Transaction + +from .helpers import vectors_from_file +from .spec import PointG1, PointG2, Spec, ref_spec_2537 + +REFERENCE_SPEC_GIT_PATH = ref_spec_2537.git_path +REFERENCE_SPEC_VERSION = ref_spec_2537.version + +pytestmark = [ + pytest.mark.valid_from("Prague"), + pytest.mark.parametrize("precompile_address", [Spec.PAIRING], ids=[""]), +] + + +@pytest.mark.parametrize( + "input,expected_output", + vectors_from_file("pairing_check_bls.json") + + [ + pytest.param( + Spec.INF_G1 + Spec.INF_G2, + Spec.PAIRING_TRUE, + id="inf_pair", + ), + pytest.param( + (Spec.INF_G1 + Spec.INF_G2) * 1000, + Spec.PAIRING_TRUE, + id="multi_inf_pair", + ), + ], +) +def test_valid( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Test the BLS12_PAIRING precompile. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) + + +@pytest.mark.parametrize( + "input", + vectors_from_file("fail-pairing_check_bls.json") + + [ + pytest.param( + PointG1(Spec.P, 0) + Spec.INF_G2, + id="g1_P_g2_inf_1", + ), + pytest.param( + PointG1(0, Spec.P) + Spec.INF_G2, + id="g1_P_g2_inf_2", + ), + pytest.param( + Spec.INF_G1 + PointG2((Spec.P, 0), (0, 0)), + id="g1_inf_g2_P_1", + ), + pytest.param( + Spec.INF_G1 + PointG2((0, Spec.P), (0, 0)), + id="g1_inf_g2_P_2", + ), + pytest.param( + Spec.INF_G1 + PointG2((0, 0), (Spec.P, 0)), + id="g1_inf_g2_P_3", + ), + pytest.param( + Spec.INF_G1 + PointG2((0, 0), (0, Spec.P)), + id="g1_inf_g2_P_4", + ), + pytest.param( + b"\x80" + bytes(Spec.INF_G1)[1:] + Spec.INF_G2, + id="invalid_encoding_g1", + ), + pytest.param( + Spec.INF_G1 + b"\x80" + bytes(Spec.INF_G2)[1:], + id="invalid_encoding_g2", + ), + pytest.param( + (Spec.INF_G1 + Spec.INF_G2) * 1000 + PointG1(Spec.P, 0) + Spec.INF_G2, + id="multi_inf_plus_g1_P_g2_inf_1", + ), + pytest.param( + Spec.P1_NOT_IN_SUBGROUP + Spec.INF_G2, + id="P1_not_in_subgroup", + ), + pytest.param( + Spec.INF_G1 + Spec.P2_NOT_IN_SUBGROUP, + id="P2_not_in_subgroup", + ), + # Input length tests can be found in ./test_bls12_variable_length_input_contracts.py + ], +) +@pytest.mark.parametrize("expected_output", [Spec.INVALID], ids=[""]) +def test_invalid( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Negative tests for the BLS12_PAIRING precompile. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) + + +@pytest.mark.parametrize( + "call_opcode", + [ + Op.STATICCALL, + Op.DELEGATECALL, + Op.CALLCODE, + ], +) +@pytest.mark.parametrize( + "input,expected_output", + [ + pytest.param( + Spec.INF_G1 + Spec.INF_G2, + Spec.PAIRING_TRUE, + id="inf_pair", + ), + ], +) +def test_call_types( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Test the BLS12_PAIRING precompile using different call types. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) diff --git a/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_precompiles_before_fork.py b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_precompiles_before_fork.py new file mode 100644 index 0000000000..d5ae6bb96d --- /dev/null +++ b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_precompiles_before_fork.py @@ -0,0 +1,86 @@ +""" +abstract: Tests BLS12 precompiles of [EIP-2537: Precompile for BLS12-381 curve operations](https://eips.ethereum.org/EIPS/eip-2537) + Tests BLS12 precompiles of [EIP-2537: Precompile for BLS12-381 curve operations](https://eips.ethereum.org/EIPS/eip-2537) + before the Prague hard fork is active +""" # noqa: E501 + +import pytest + +from ethereum_test_tools import Environment, StateTestFiller, Transaction + +from .spec import FP, FP2, Scalar, Spec, ref_spec_2537 + +REFERENCE_SPEC_GIT_PATH = ref_spec_2537.git_path +REFERENCE_SPEC_VERSION = ref_spec_2537.version + +pytestmark = pytest.mark.valid_at_transition_to("Prague") + + +@pytest.mark.parametrize( + "precompile_address,input", + [ + pytest.param( + Spec.G1ADD, + Spec.INF_G1 + Spec.INF_G1, + id="G1ADD", + ), + pytest.param( + Spec.G1MSM, + Spec.INF_G1 + Scalar(0), + id="G1MSM", + ), + pytest.param( + Spec.G1MUL, + Spec.INF_G1 + Scalar(0), + id="G1MUL", + ), + pytest.param( + Spec.G2ADD, + Spec.INF_G2 + Spec.INF_G2, + id="G2ADD", + ), + pytest.param( + Spec.G2MSM, + Spec.INF_G2 + Scalar(0), + id="G2MSM", + ), + pytest.param( + Spec.G2MUL, + Spec.INF_G2 + Scalar(0), + id="G2MUL", + ), + pytest.param( + Spec.PAIRING, + Spec.INF_G1 + Spec.INF_G2, + id="PAIRING", + ), + pytest.param( + Spec.MAP_FP_TO_G1, + FP(0), + id="MAP_FP_TO_G1", + ), + pytest.param( + Spec.MAP_FP2_TO_G2, + FP2((0, 0)), + id="MAP_FP2_TO_G2", + ), + ], +) +@pytest.mark.parametrize("expected_output,call_succeeds", [pytest.param(b"", True, id="")]) +def test_precompile_before_fork( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Test all BLS12 precompiles before the Prague hard fork is active. + + The call must succeed but the output must be empty. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) diff --git a/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_variable_length_input_contracts.py b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_variable_length_input_contracts.py new file mode 100644 index 0000000000..159cb083ca --- /dev/null +++ b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_variable_length_input_contracts.py @@ -0,0 +1,466 @@ +""" +abstract: Tests minimum gas and input length for BLS12_G1MSM, BLS12_G2MSM, BLS12_PAIRING precompiles of [EIP-2537: Precompile for BLS12-381 curve operations](https://eips.ethereum.org/EIPS/eip-2537) + Tests minimum gas and input length for BLS12_G1MSM, BLS12_G2MSM, BLS12_PAIRING precompiles of [EIP-2537: Precompile for BLS12-381 curve operations](https://eips.ethereum.org/EIPS/eip-2537). +""" # noqa: E501 + +from typing import List, SupportsBytes + +import pytest + +from ethereum_test_tools import Environment +from ethereum_test_tools import Opcodes as Op +from ethereum_test_tools import StateTestFiller, Storage, Transaction + +from .spec import GAS_CALCULATION_FUNCTION_MAP, PointG1, PointG2, Scalar, Spec, ref_spec_2537 + +REFERENCE_SPEC_GIT_PATH = ref_spec_2537.git_path +REFERENCE_SPEC_VERSION = ref_spec_2537.version + +pytestmark = pytest.mark.valid_from("Prague") + +G1_MSM_K_INPUT_LENGTH = len(PointG1() + Scalar()) +G2_MSM_K_INPUT_LENGTH = len(PointG2() + Scalar()) +G1_GAS = GAS_CALCULATION_FUNCTION_MAP[Spec.G1MSM] +G2_GAS = GAS_CALCULATION_FUNCTION_MAP[Spec.G2MSM] +PAIRING_GAS = GAS_CALCULATION_FUNCTION_MAP[Spec.PAIRING] +PAIRINGS_TO_TEST = 20 + + +@pytest.fixture +def input() -> bytes: + """Input data for the contract.""" + return b"" + + +@pytest.fixture +def call_contract_code( + precompile_address: int, + precompile_gas_list: List[int], + precompile_data_length_list: List[int], + expected_output: bytes | SupportsBytes, + call_opcode: Op, + call_contract_post_storage: Storage, +) -> bytes: + """ + Code of the test contract to validate minimum expected gas in precompiles, as well as + expected input lengths on all variable-length input precompiles. + + Code differs from the one used in all other tests in this file, because it accepts a list of + precompile gas values and a list of precompile data lengths, and for each pair of values, it + calls the precompile with the given gas and data length, data being passed to the precompile + is all zeros. + + Args: + precompile_address: + Address of the precompile to call. + precompile_gas_list: + List of gas values to be used to call the precompile, one for each call. + precompile_data_length_list: + List of data lengths to be used to call the precompile, one for each call. + expected_output: + Expected output of the contract, it is only used to determine if the call is expected + to succeed or fail. + call_opcode: + Type of call used to call the precompile (Op.CALL, Op.CALLCODE, Op.DELEGATECALL, + Op.STATICCALL). + call_contract_post_storage: + Storage of the test contract after the transaction is executed. + """ + expected_output = bytes(expected_output) + + # Depending on the expected output, we can deduce if the call is expected to succeed or fail. + call_succeeds = len(expected_output) > 0 + + assert len(precompile_gas_list) == len(precompile_data_length_list) + + assert call_opcode in [Op.CALL, Op.CALLCODE, Op.DELEGATECALL, Op.STATICCALL] + value = [0] if call_opcode in [Op.CALL, Op.CALLCODE] else [] + + code = b"" + for precompile_gas, precompile_args_length in zip( + precompile_gas_list, precompile_data_length_list + ): + # For each given precompile gas value, and given arguments length, call the precompile + # with the given gas and call data (all zeros) and compare the result. + code += Op.SSTORE( + call_contract_post_storage.store_next(1 if call_succeeds else 0), + Op.CALL( + precompile_gas, + precompile_address, + *value, # Optional, only used for CALL and CALLCODE. + 0, + precompile_args_length, # Memory is empty, so we pass zeros. + 0, + 0, + ), + ) + return code + + +@pytest.mark.parametrize( + "precompile_gas_list,precompile_data_length_list", + [ + pytest.param( + [G1_GAS(i * G1_MSM_K_INPUT_LENGTH) for i in range(1, len(Spec.MSM_DISCOUNT_TABLE))], + [i * G1_MSM_K_INPUT_LENGTH for i in range(1, len(Spec.MSM_DISCOUNT_TABLE))], + id="exact_gas_full_discount_table", + ), + pytest.param( + [ + G1_GAS(i * G1_MSM_K_INPUT_LENGTH) + 1 + for i in range(1, len(Spec.MSM_DISCOUNT_TABLE)) + ], + [i * G1_MSM_K_INPUT_LENGTH for i in range(1, len(Spec.MSM_DISCOUNT_TABLE))], + id="one_extra_gas_full_discount_table", + ), + ], +) +@pytest.mark.parametrize("expected_output", [PointG1()], ids=[""]) +@pytest.mark.parametrize("tx_gas_limit", [100_000_000], ids=[""]) +@pytest.mark.parametrize("precompile_address", [Spec.G1MSM]) +def test_valid_gas_g1msm( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Test the BLS12_G1MSM discount gas table in full, by expecting the call to succeed for + all possible input lengths because the appropriate amount of gas is provided. + + If any of the calls fail, the test will fail. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) + + +@pytest.mark.parametrize( + "precompile_gas_list,precompile_data_length_list", + [ + pytest.param( + [0], + [G1_MSM_K_INPUT_LENGTH], + id="zero_gas_passed", + ), + pytest.param( + [ + G1_GAS(i * G1_MSM_K_INPUT_LENGTH) - 1 + for i in range(1, len(Spec.MSM_DISCOUNT_TABLE)) + ], + [i * G1_MSM_K_INPUT_LENGTH for i in range(1, len(Spec.MSM_DISCOUNT_TABLE))], + id="insufficient_gas_full_discount_table", + ), + ], +) +@pytest.mark.parametrize("expected_output", [Spec.INVALID], ids=[""]) +@pytest.mark.parametrize("tx_gas_limit", [100_000_000], ids=[""]) +@pytest.mark.parametrize("precompile_address", [Spec.G1MSM]) +def test_invalid_gas_g1msm( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Test the BLS12_G1MSM discount gas table in full, by expecting the call to fail for + all possible input lengths because the appropriate amount of gas is not provided. + + If any of the calls succeeds, the test will fail. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) + + +@pytest.mark.parametrize( + "precompile_gas_list,precompile_data_length_list", + [ + pytest.param( + [G1_GAS(G1_MSM_K_INPUT_LENGTH)], + [0], + id="zero_length_input", + ), + pytest.param( + [G1_GAS(i * G1_MSM_K_INPUT_LENGTH) for i in range(1, len(Spec.MSM_DISCOUNT_TABLE))], + [(i * G1_MSM_K_INPUT_LENGTH) - 1 for i in range(1, len(Spec.MSM_DISCOUNT_TABLE))], + id="input_one_byte_too_short_full_discount_table", + ), + pytest.param( + [G1_GAS(i * G1_MSM_K_INPUT_LENGTH) for i in range(1, len(Spec.MSM_DISCOUNT_TABLE))], + [(i * G1_MSM_K_INPUT_LENGTH) + 1 for i in range(1, len(Spec.MSM_DISCOUNT_TABLE))], + id="input_one_byte_too_long_full_discount_table", + ), + ], +) +@pytest.mark.parametrize("expected_output", [Spec.INVALID], ids=[""]) +@pytest.mark.parametrize("tx_gas_limit", [100_000_000], ids=[""]) +@pytest.mark.parametrize("precompile_address", [Spec.G1MSM]) +def test_invalid_length_g1msm( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Test the BLS12_G1MSM discount gas table in full, by expecting the call to fail for + all possible input lengths provided because they are too long or short, or zero length. + + If any of the calls succeeds, the test will fail. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) + + +@pytest.mark.parametrize( + "precompile_gas_list,precompile_data_length_list", + [ + pytest.param( + [G2_GAS(i * G2_MSM_K_INPUT_LENGTH) for i in range(1, len(Spec.MSM_DISCOUNT_TABLE))], + [i * G2_MSM_K_INPUT_LENGTH for i in range(1, len(Spec.MSM_DISCOUNT_TABLE))], + id="exact_gas_full_discount_table", + ), + pytest.param( + [ + G2_GAS(i * G2_MSM_K_INPUT_LENGTH) + 1 + for i in range(1, len(Spec.MSM_DISCOUNT_TABLE)) + ], + [i * G2_MSM_K_INPUT_LENGTH for i in range(1, len(Spec.MSM_DISCOUNT_TABLE))], + id="one_extra_gas_full_discount_table", + ), + ], +) +@pytest.mark.parametrize("expected_output", [PointG2()], ids=[""]) +@pytest.mark.parametrize("tx_gas_limit", [100_000_000], ids=[""]) +@pytest.mark.parametrize("precompile_address", [Spec.G2MSM]) +def test_valid_gas_g2msm( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Test the BLS12_G2MSM discount gas table in full, by expecting the call to succeed for + all possible input lengths because the appropriate amount of gas is provided. + + If any of the calls fail, the test will fail. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) + + +@pytest.mark.parametrize( + "precompile_gas_list,precompile_data_length_list", + [ + pytest.param( + [0], + [G2_MSM_K_INPUT_LENGTH], + id="zero_gas_passed", + ), + pytest.param( + [ + G2_GAS(i * G2_MSM_K_INPUT_LENGTH) - 1 + for i in range(1, len(Spec.MSM_DISCOUNT_TABLE)) + ], + [i * G2_MSM_K_INPUT_LENGTH for i in range(1, len(Spec.MSM_DISCOUNT_TABLE))], + id="insufficient_gas_full_discount_table", + ), + ], +) +@pytest.mark.parametrize("expected_output", [Spec.INVALID], ids=[""]) +@pytest.mark.parametrize("tx_gas_limit", [100_000_000], ids=[""]) +@pytest.mark.parametrize("precompile_address", [Spec.G2MSM]) +def test_invalid_gas_g2msm( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Test the BLS12_G2MSM discount gas table in full, by expecting the call to fail for + all possible input lengths because the appropriate amount of gas is not provided. + + If any of the calls succeeds, the test will fail. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) + + +@pytest.mark.parametrize( + "precompile_gas_list,precompile_data_length_list", + [ + pytest.param( + [G2_GAS(G2_MSM_K_INPUT_LENGTH)], + [0], + id="zero_length_input", + ), + pytest.param( + [G2_GAS(i * G2_MSM_K_INPUT_LENGTH) for i in range(1, len(Spec.MSM_DISCOUNT_TABLE))], + [(i * G2_MSM_K_INPUT_LENGTH) - 1 for i in range(1, len(Spec.MSM_DISCOUNT_TABLE))], + id="input_one_byte_too_short_full_discount_table", + ), + pytest.param( + [G2_GAS(i * G2_MSM_K_INPUT_LENGTH) for i in range(1, len(Spec.MSM_DISCOUNT_TABLE))], + [(i * G2_MSM_K_INPUT_LENGTH) + 1 for i in range(1, len(Spec.MSM_DISCOUNT_TABLE))], + id="input_one_byte_too_long_full_discount_table", + ), + ], +) +@pytest.mark.parametrize("expected_output", [Spec.INVALID], ids=[""]) +@pytest.mark.parametrize("tx_gas_limit", [100_000_000], ids=[""]) +@pytest.mark.parametrize("precompile_address", [Spec.G2MSM]) +def test_invalid_length_g2msm( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Test the BLS12_G2MSM discount gas table in full, by expecting the call to fail for + all possible input lengths provided because they are too long or short, or zero length. + + If any of the calls succeeds, the test will fail. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) + + +@pytest.mark.parametrize( + "precompile_gas_list,precompile_data_length_list", + [ + pytest.param( + [PAIRING_GAS(i * Spec.LEN_PER_PAIR) for i in range(1, PAIRINGS_TO_TEST + 1)], + [i * Spec.LEN_PER_PAIR for i in range(1, PAIRINGS_TO_TEST + 1)], + id="sufficient_gas", + ), + pytest.param( + [PAIRING_GAS(i * Spec.LEN_PER_PAIR) + 1 for i in range(1, PAIRINGS_TO_TEST + 1)], + [i * Spec.LEN_PER_PAIR for i in range(1, PAIRINGS_TO_TEST + 1)], + id="extra_gas", + ), + ], +) +@pytest.mark.parametrize("expected_output", [Spec.PAIRING_TRUE], ids=[""]) +@pytest.mark.parametrize("tx_gas_limit", [100_000_000], ids=[""]) +@pytest.mark.parametrize("precompile_address", [Spec.PAIRING]) +def test_valid_gas_pairing( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Test the BLS12_PAIRING precompile, by expecting the call to succeed for all possible input + lengths (up to k == PAIRINGS_TO_TEST). + + If any of the calls fails, the test will fail. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) + + +@pytest.mark.parametrize( + "precompile_gas_list,precompile_data_length_list", + [ + pytest.param( + [0], + [Spec.LEN_PER_PAIR], + id="zero_gas_passed", + ), + pytest.param( + [PAIRING_GAS(i * Spec.LEN_PER_PAIR) - 1 for i in range(1, PAIRINGS_TO_TEST + 1)], + [i * Spec.LEN_PER_PAIR for i in range(1, PAIRINGS_TO_TEST + 1)], + id="insufficient_gas", + ), + ], +) +@pytest.mark.parametrize("expected_output", [Spec.INVALID], ids=[""]) +@pytest.mark.parametrize("tx_gas_limit", [100_000_000], ids=[""]) +@pytest.mark.parametrize("precompile_address", [Spec.PAIRING]) +def test_invalid_gas_pairing( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Test the BLS12_PAIRING precompile, by expecting the call to fail for all possible input + lengths (up to k == PAIRINGS_TO_TEST) because the appropriate amount of gas is not provided. + + If any of the calls succeeds, the test will fail. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) + + +@pytest.mark.parametrize( + "precompile_gas_list,precompile_data_length_list", + [ + pytest.param( + [PAIRING_GAS(Spec.LEN_PER_PAIR)], + [0], + id="zero_length", + ), + pytest.param( + [PAIRING_GAS(i * Spec.LEN_PER_PAIR) for i in range(1, PAIRINGS_TO_TEST + 1)], + [(i * Spec.LEN_PER_PAIR) - 1 for i in range(1, PAIRINGS_TO_TEST + 1)], + id="input_too_short", + ), + pytest.param( + [PAIRING_GAS(i * Spec.LEN_PER_PAIR) for i in range(1, PAIRINGS_TO_TEST + 1)], + [(i * Spec.LEN_PER_PAIR) + 1 for i in range(1, PAIRINGS_TO_TEST + 1)], + id="input_too_long", + ), + ], +) +@pytest.mark.parametrize("expected_output", [Spec.INVALID], ids=[""]) +@pytest.mark.parametrize("tx_gas_limit", [100_000_000], ids=[""]) +@pytest.mark.parametrize("precompile_address", [Spec.PAIRING]) +def test_invalid_length_pairing( + state_test: StateTestFiller, + pre: dict, + post: dict, + tx: Transaction, +): + """ + Test the BLS12_PAIRING precompile, by expecting the call to fail for all possible input + lengths (up to k == PAIRINGS_TO_TEST) because the incorrect input length was used. + + If any of the calls succeeds, the test will fail. + """ + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) diff --git a/tests/prague/eip2537_bls_12_381_precompiles/vectors/add_G1_bls.json b/tests/prague/eip2537_bls_12_381_precompiles/vectors/add_G1_bls.json new file mode 100644 index 0000000000..b3112c4c19 --- /dev/null +++ b/tests/prague/eip2537_bls_12_381_precompiles/vectors/add_G1_bls.json @@ -0,0 +1,65 @@ +[ + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000112b98340eee2777cc3c14163dea3ec97977ac3dc5c70da32e6e87578f44912e902ccef9efe28d4a78b8999dfbca942600000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a21", + "Name": "bls_g1add_g1+p1", + "Expected": "000000000000000000000000000000000a40300ce2dec9888b60690e9a41d3004fda4886854573974fab73b046d3147ba5b7a5bde85279ffede1b45b3918d82d0000000000000000000000000000000006d3d887e9f53b9ec4eb6cedf5607226754b07c01ace7834f57f3e7315faefb739e59018e22c492006190fba4a870025", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000112b98340eee2777cc3c14163dea3ec97977ac3dc5c70da32e6e87578f44912e902ccef9efe28d4a78b8999dfbca942600000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a210000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1", + "Name": "bls_g1add_p1+g1", + "Expected": "000000000000000000000000000000000a40300ce2dec9888b60690e9a41d3004fda4886854573974fab73b046d3147ba5b7a5bde85279ffede1b45b3918d82d0000000000000000000000000000000006d3d887e9f53b9ec4eb6cedf5607226754b07c01ace7834f57f3e7315faefb739e59018e22c492006190fba4a870025", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef00000000000000000000000000000000193fb7cedb32b2c3adc06ec11a96bc0d661869316f5e4a577a9f7c179593987beb4fb2ee424dbb2f5dd891e228b46c4a0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1", + "Name": "bls_g1add_g1_wrong_order+g1", + "Expected": "000000000000000000000000000000000abe7ae4ae2b092a5cc1779b1f5605d904fa6ec59b0f084907d1f5e4d2663e117a3810e027210a72186159a21271df3e0000000000000000000000000000000001e1669f00e10205f2e2f1195d65c21022f6a9a6de21f329756309815281a4434b2864d34ebcbc1d7e7cfaaee3feeea2", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Name": "bls_g1add_(g1+0=g1)", + "Expected": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000112b98340eee2777cc3c14163dea3ec97977ac3dc5c70da32e6e87578f44912e902ccef9efe28d4a78b8999dfbca942600000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a210000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Name": "bls_g1add_(p1+0=p1)", + "Expected": "00000000000000000000000000000000112b98340eee2777cc3c14163dea3ec97977ac3dc5c70da32e6e87578f44912e902ccef9efe28d4a78b8999dfbca942600000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a21", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb00000000000000000000000000000000114d1d6855d545a8aa7d76c8cf2e21f267816aef1db507c96655b9d5caac42364e6f38ba0ecb751bad54dcd6b939c2ca", + "Name": "bls_g1add_(g1-g1=0)", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000112b98340eee2777cc3c14163dea3ec97977ac3dc5c70da32e6e87578f44912e902ccef9efe28d4a78b8999dfbca942600000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a2100000000000000000000000000000000112b98340eee2777cc3c14163dea3ec97977ac3dc5c70da32e6e87578f44912e902ccef9efe28d4a78b8999dfbca9426000000000000000000000000000000000195e911162921ba5ed055b496420f197693d36569ec34c63d7c0529a097d49e543070afba4b707e878e53c2b779208a", + "Name": "bls_g1add_(p1-p1=0)", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1", + "Name": "bls_g1add_(g1+g1=2*g1)", + "Expected": "000000000000000000000000000000000572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e00000000000000000000000000000000166a9d8cabc673a322fda673779d8e3822ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d28", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000112b98340eee2777cc3c14163dea3ec97977ac3dc5c70da32e6e87578f44912e902ccef9efe28d4a78b8999dfbca942600000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a2100000000000000000000000000000000112b98340eee2777cc3c14163dea3ec97977ac3dc5c70da32e6e87578f44912e902ccef9efe28d4a78b8999dfbca942600000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a21", + "Name": "bls_g1add_(p1+p1=2*p1)", + "Expected": "0000000000000000000000000000000015222cddbabdd764c4bee0b3720322a65ff4712c86fc4b1588d0c209210a0884fa9468e855d261c483091b2bf7de6a630000000000000000000000000000000009f9edb99bc3b75d7489735c98b16ab78b9386c5f7a1f76c7e96ac6eb5bbde30dbca31a74ec6e0f0b12229eecea33c39", + "Gas": 500, + "NoBenchmark": false + } +] \ No newline at end of file diff --git a/tests/prague/eip2537_bls_12_381_precompiles/vectors/add_G2_bls.json b/tests/prague/eip2537_bls_12_381_precompiles/vectors/add_G2_bls.json new file mode 100644 index 0000000000..630ff71789 --- /dev/null +++ b/tests/prague/eip2537_bls_12_381_precompiles/vectors/add_G2_bls.json @@ -0,0 +1,65 @@ +[ + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be00000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d878451", + "Name": "bls_g2add_g2+p2", + "Expected": "000000000000000000000000000000000b54a8a7b08bd6827ed9a797de216b8c9057b3a9ca93e2f88e7f04f19accc42da90d883632b9ca4dc38d013f71ede4db00000000000000000000000000000000077eba4eecf0bd764dce8ed5f45040dd8f3b3427cb35230509482c14651713282946306247866dfe39a8e33016fcbe520000000000000000000000000000000014e60a76a29ef85cbd69f251b9f29147b67cfe3ed2823d3f9776b3a0efd2731941d47436dc6d2b58d9e65f8438bad073000000000000000000000000000000001586c3c910d95754fef7a732df78e279c3d37431c6a2b77e67a00c7c130a8fcd4d19f159cbeb997a178108fffffcbd20", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d87845100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be", + "Name": "bls_g2add_p2+g2", + "Expected": "000000000000000000000000000000000b54a8a7b08bd6827ed9a797de216b8c9057b3a9ca93e2f88e7f04f19accc42da90d883632b9ca4dc38d013f71ede4db00000000000000000000000000000000077eba4eecf0bd764dce8ed5f45040dd8f3b3427cb35230509482c14651713282946306247866dfe39a8e33016fcbe520000000000000000000000000000000014e60a76a29ef85cbd69f251b9f29147b67cfe3ed2823d3f9776b3a0efd2731941d47436dc6d2b58d9e65f8438bad073000000000000000000000000000000001586c3c910d95754fef7a732df78e279c3d37431c6a2b77e67a00c7c130a8fcd4d19f159cbeb997a178108fffffcbd20", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000197bfd0342bbc8bee2beced2f173e1a87be576379b343e93232d6cef98d84b1d696e5612ff283ce2cfdccb2cfb65fa0c00000000000000000000000000000000184e811f55e6f9d84d77d2f79102fd7ea7422f4759df5bf7f6331d550245e3f1bcf6a30e3b29110d85e0ca16f9f6ae7a000000000000000000000000000000000f10e1eb3c1e53d2ad9cf2d398b2dc22c5842fab0a74b174f691a7e914975da3564d835cd7d2982815b8ac57f507348f000000000000000000000000000000000767d1c453890f1b9110fda82f5815c27281aba3f026ee868e4176a0654feea41a96575e0c4d58a14dbfbcc05b5010b100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be", + "Name": "bls_g2add_g2_wrong_order+g2", + "Expected": "0000000000000000000000000000000011f00077935238fc57086414804303b20fab5880bc29f35ebda22c13dd44e586c8a889fe2ba799082c8458d861ac10cf0000000000000000000000000000000007318be09b19be000fe5df77f6e664a8286887ad8373005d7f7a203fcc458c28004042780146d3e43fa542d921c69512000000000000000000000000000000001287eab085d6f8a29f1f1aedb5ad9e8546963f0b11865e05454d86b9720c281db567682a233631f63a2794432a5596ae0000000000000000000000000000000012ec87cea1bacb75aa97728bcd64b27c7a42dd2319a2e17fe3837a05f85d089c5ebbfb73c1d08b7007e2b59ec9c8e065", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Name": "bls_g2add_(g2+0=g2)", + "Expected": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d87845100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Name": "bls_g2add_(p2+0=p2)", + "Expected": "00000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d878451", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000d1b3cc2c7027888be51d9ef691d77bcb679afda66c73f17f9ee3837a55024f78c71363275a75d75d86bab79f74782aa0000000000000000000000000000000013fa4d4a0ad8b1ce186ed5061789213d993923066dddaf1040bc3ff59f825c78df74f2d75467e25e0f55f8a00fa030ed", + "Name": "bls_g2add_(g2-g2=0)", + "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d87845100000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000a6296409115572426717c73668335a949829d739cff2cb4ab043710d28f8e772f6ef41aac4806c9cb273c490384032d000000000000000000000000000000000cde4e850c721fa94e8890d500e3655b442d5c0dc4fff1b694c6f8dd68f6d8dc1bc3251a37d27e7af96f65a96278265a", + "Name": "bls_g2add_(p2-p2=0)", + "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be", + "Name": "bls_g2add_(g2+g2=2*g2)", + "Expected": "000000000000000000000000000000001638533957d540a9d2370f17cc7ed5863bc0b995b8825e0ee1ea1e1e4d00dbae81f14b0bf3611b78c952aacab827a053000000000000000000000000000000000a4edef9c1ed7f729f520e47730a124fd70662a904ba1074728114d1031e1572c6c886f6b57ec72a6178288c47c33577000000000000000000000000000000000468fb440d82b0630aeb8dca2b5256789a66da69bf91009cbfe6bd221e47aa8ae88dece9764bf3bd999d95d71e4c9899000000000000000000000000000000000f6d4552fa65dd2638b361543f887136a43253d9c66c411697003f7a13c308f5422e1aa0a59c8967acdefd8b6e36ccf3", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d87845100000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d878451", + "Name": "bls_g2add_(p2+p2=2*p2)", + "Expected": "000000000000000000000000000000000b76fcbb604082a4f2d19858a7befd6053fa181c5119a612dfec83832537f644e02454f2b70d40985ebb08042d1620d40000000000000000000000000000000019a4a02c0ae51365d964c73be7babb719db1c69e0ddbf9a8a335b5bed3b0a4b070d2d5df01d2da4a3f1e56aae2ec106d000000000000000000000000000000000d18322f821ac72d3ca92f92b000483cf5b7d9e5d06873a44071c4e7e81efd904f210208fe0b9b4824f01c65bc7e62080000000000000000000000000000000004e563d53609a2d1e216aaaee5fbc14ef460160db8d1fdc5e1bd4e8b54cd2f39abf6f925969fa405efb9e700b01c7085", + "Gas": 800, + "NoBenchmark": false + } +] \ No newline at end of file diff --git a/tests/prague/eip2537_bls_12_381_precompiles/vectors/fail-add_G1_bls.json b/tests/prague/eip2537_bls_12_381_precompiles/vectors/fail-add_G1_bls.json new file mode 100644 index 0000000000..e61e269d21 --- /dev/null +++ b/tests/prague/eip2537_bls_12_381_precompiles/vectors/fail-add_G1_bls.json @@ -0,0 +1,32 @@ +[ + { + "Input": "", + "ExpectedError": "invalid input length", + "Name": "bls_g1add_empty_input" + }, + { + "Input": "00000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000112b98340eee2777cc3c14163dea3ec97977ac3dc5c70da32e6e87578f44912e902ccef9efe28d4a78b8999dfbca942600000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a21", + "ExpectedError": "invalid input length", + "Name": "bls_g1add_short_input" + }, + { + "Input": "000000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000112b98340eee2777cc3c14163dea3ec97977ac3dc5c70da32e6e87578f44912e902ccef9efe28d4a78b8999dfbca942600000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a21", + "ExpectedError": "invalid input length", + "Name": "bls_g1add_large_input" + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb00000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a2100000000000000000000000000000000112b98340eee2777cc3c14163dea3ec97977ac3dc5c70da32e6e87578f44912e902ccef9efe28d4a78b8999dfbca942600000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a21", + "ExpectedError": "invalid point: not on curve", + "Name": "bls_g1add_point_not_on_curve" + }, + { + "Input": "0000000000000000000000000000000031f2e5916b17be2e71b10b4292f558e727dfd7d48af9cbc5087f0ce00dcca27c8b01e83eaace1aefb539f00adb2271660000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000112b98340eee2777cc3c14163dea3ec97977ac3dc5c70da32e6e87578f44912e902ccef9efe28d4a78b8999dfbca942600000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a21", + "ExpectedError": "invalid fp.Element encoding", + "Name": "bls_g2add_invalid_field_element" + }, + { + "Input": "1000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000112b98340eee2777cc3c14163dea3ec97977ac3dc5c70da32e6e87578f44912e902ccef9efe28d4a78b8999dfbca942600000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a21", + "ExpectedError": "invalid field element top bytes", + "Name": "bls_g1add_violate_top_bytes" + } +] \ No newline at end of file diff --git a/tests/prague/eip2537_bls_12_381_precompiles/vectors/fail-add_G2_bls.json b/tests/prague/eip2537_bls_12_381_precompiles/vectors/fail-add_G2_bls.json new file mode 100644 index 0000000000..9d3ab9c18a --- /dev/null +++ b/tests/prague/eip2537_bls_12_381_precompiles/vectors/fail-add_G2_bls.json @@ -0,0 +1,32 @@ +[ + { + "Input": "", + "ExpectedError": "invalid input length", + "Name": "bls_g2add_empty_input" + }, + { + "Input": "000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be00000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d878451", + "ExpectedError": "invalid input length", + "Name": "bls_g2add_short_input" + }, + { + "Input": "0000000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be00000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d878451", + "ExpectedError": "invalid input length", + "Name": "bls_g2add_long_input" + }, + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb800000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be00000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d878451", + "ExpectedError": "invalid point: not on curve", + "Name": "bls_g2add_point_not_on_curve" + }, + { + "Input": "000000000000000000000000000000001c4bb49d2a0ef12b7123acdd7110bd292b5bc659edc54dc21b81de057194c79b2a5803255959bbef8e7f56c8c12168630000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be00000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d878451", + "ExpectedError": "invalid fp.Element encoding", + "Name": "bls_g2add_invalid_field_element" + }, + { + "Input": "10000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be00000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d878451", + "ExpectedError": "invalid field element top bytes", + "Name": "bls_g2add_violate_top_bytes" + } +] \ No newline at end of file diff --git a/tests/prague/eip2537_bls_12_381_precompiles/vectors/fail-map_fp2_to_G2_bls.json b/tests/prague/eip2537_bls_12_381_precompiles/vectors/fail-map_fp2_to_G2_bls.json new file mode 100644 index 0000000000..4411fdcc0f --- /dev/null +++ b/tests/prague/eip2537_bls_12_381_precompiles/vectors/fail-map_fp2_to_G2_bls.json @@ -0,0 +1,27 @@ +[ + { + "Input": "", + "ExpectedError": "invalid input length", + "Name": "bls_mapg2_empty_input" + }, + { + "Input": "0000000000000000000000000000000007355d25caf6e7f2f0cb2812ca0e513bd026ed09dda65b177500fa31714e09ea0ded3a078b526bed3307f804d4b93b040000000000000000000000000000000002829ce3c021339ccb5caf3e187f6370e1e2a311dec9b75363117063ab2015603ff52c3d3b98f19c2f65575e99e8b7", + "ExpectedError": "invalid input length", + "Name": "bls_mapg2_short_input" + }, + { + "Input": "000000000000000000000000000000000007355d25caf6e7f2f0cb2812ca0e513bd026ed09dda65b177500fa31714e09ea0ded3a078b526bed3307f804d4b93b040000000000000000000000000000000002829ce3c021339ccb5caf3e187f6370e1e2a311dec9b75363117063ab2015603ff52c3d3b98f19c2f65575e99e8b78c", + "ExpectedError": "invalid input length", + "Name": "bls_mapg2_long_input" + }, + { + "Input": "000000000000000000000000000000000007355d25caf6e7f2f0cb2812ca0e513bd026ed09dda65b177500fa31714e09ea0ded3a078b526bed3307f804d4b93b040000000000000000000000000000000002829ce3c021339ccb5caf3e187f6370e1e2a311dec9b75363117063ab2015603ff52c3d3b98f19c2f65575e99e8b7", + "ExpectedError": "invalid field element top bytes", + "Name": "bls_mapg2_top_bytes" + }, + { + "Input": "0000000000000000000000000000000021366f100476ce8d3be6cfc90d59fe13349e388ed12b6dd6dc31ccd267ff000e2c993a063ca66beced06f804d4b8e5af0000000000000000000000000000000002829ce3c021339ccb5caf3e187f6370e1e2a311dec9b75363117063ab2015603ff52c3d3b98f19c2f65575e99e8b78c", + "ExpectedError": "invalid fp.Element encoding", + "Name": "bls_mapg2_invalid_fq_element" + } +] \ No newline at end of file diff --git a/tests/prague/eip2537_bls_12_381_precompiles/vectors/fail-map_fp_to_G1_bls.json b/tests/prague/eip2537_bls_12_381_precompiles/vectors/fail-map_fp_to_G1_bls.json new file mode 100644 index 0000000000..2f66856931 --- /dev/null +++ b/tests/prague/eip2537_bls_12_381_precompiles/vectors/fail-map_fp_to_G1_bls.json @@ -0,0 +1,27 @@ +[ + { + "Input": "", + "ExpectedError": "invalid input length", + "Name": "bls_mapg1_empty_input" + }, + { + "Input": "00000000000000000000000000000000156c8a6a2c184569d69a76be144b5cdc5141d2d2ca4fe341f011e25e3969c55ad9e9b9ce2eb833c81a908e5fa4ac5f", + "ExpectedError": "invalid input length", + "Name": "bls_mapg1_short_input" + }, + { + "Input": "0000000000000000000000000000000000156c8a6a2c184569d69a76be144b5cdc5141d2d2ca4fe341f011e25e3969c55ad9e9b9ce2eb833c81a908e5fa4ac5f03", + "ExpectedError": "invalid input length", + "Name": "bls_mapg1_large_input" + }, + { + "Input": "1000000000000000000000000000000000156c8a6a2c184569d69a76be144b5cdc5141d2d2ca4fe341f011e25e3969c55ad9e9b9ce2eb833c81a908e5fa4ac5f", + "ExpectedError": "invalid field element top bytes", + "Name": "bls_mapg1_top_bytes" + }, + { + "Input": "000000000000000000000000000000002f6d9c5465982c0421b61e74579709b3b5b91e57bdd4f6015742b4ff301abb7ef895b9cce00c33c7d48f8e5fa4ac09ae", + "ExpectedError": "invalid fp.Element encoding", + "Name": "bls_invalid_fq_element" + } +] \ No newline at end of file diff --git a/tests/prague/eip2537_bls_12_381_precompiles/vectors/fail-mul_G1_bls.json b/tests/prague/eip2537_bls_12_381_precompiles/vectors/fail-mul_G1_bls.json new file mode 100644 index 0000000000..5ae8e3b536 --- /dev/null +++ b/tests/prague/eip2537_bls_12_381_precompiles/vectors/fail-mul_G1_bls.json @@ -0,0 +1,37 @@ +[ + { + "Input": "", + "ExpectedError": "invalid input length", + "Name": "bls_g1mul_empty_input" + }, + { + "Input": "00000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000002", + "ExpectedError": "invalid input length", + "Name": "bls_g1mul_short_input" + }, + { + "Input": "000000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000002", + "ExpectedError": "invalid input length", + "Name": "bls_g1mul_large_input" + }, + { + "Input": "0000000000000000000000000000000031f2e5916b17be2e71b10b4292f558e727dfd7d48af9cbc5087f0ce00dcca27c8b01e83eaace1aefb539f00adb2271660000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000002", + "ExpectedError": "invalid fp.Element encoding", + "Name": "bls_g1mul_invalid_field_element" + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb00000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a210000000000000000000000000000000000000000000000000000000000000002", + "ExpectedError": "invalid point: not on curve", + "Name": "bls_g1mul_point_not_on_curve" + }, + { + "Input": "1000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000002", + "ExpectedError": "invalid field element top bytes", + "Name": "bls_g1mul_violate_top_bytes" + }, + { + "Input": "000000000000000000000000000000000123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef00000000000000000000000000000000193fb7cedb32b2c3adc06ec11a96bc0d661869316f5e4a577a9f7c179593987beb4fb2ee424dbb2f5dd891e228b46c4a0000000000000000000000000000000000000000000000000000000000000002", + "ExpectedError": "g1 point is not on correct subgroup", + "Name": "bls_g1mul_g1_not_in_correct_subgroup" + } +] \ No newline at end of file diff --git a/tests/prague/eip2537_bls_12_381_precompiles/vectors/fail-mul_G2_bls.json b/tests/prague/eip2537_bls_12_381_precompiles/vectors/fail-mul_G2_bls.json new file mode 100644 index 0000000000..5b4fa8a1f6 --- /dev/null +++ b/tests/prague/eip2537_bls_12_381_precompiles/vectors/fail-mul_G2_bls.json @@ -0,0 +1,37 @@ +[ + { + "Input": "", + "ExpectedError": "invalid input length", + "Name": "bls_g2mul_empty_input" + }, + { + "Input": "000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000002", + "ExpectedError": "invalid input length", + "Name": "bls_g2mul_short_input" + }, + { + "Input": "0000000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000002", + "ExpectedError": "invalid input length", + "Name": "bls_g2mul_large_input" + }, + { + "Input": "000000000000000000000000000000001c4bb49d2a0ef12b7123acdd7110bd292b5bc659edc54dc21b81de057194c79b2a5803255959bbef8e7f56c8c12168630000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000002", + "ExpectedError": "invalid fp.Element encoding", + "Name": "bls_g2mul_invalid_field_element" + }, + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb800000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000002", + "ExpectedError": "invalid point: not on curve", + "Name": "bls_g2mul_point_not_on_curve" + }, + { + "Input": "10000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000002", + "ExpectedError": "invalid field element top bytes", + "Name": "bls_g2mul_violate_top_bytes" + }, + { + "Input": "00000000000000000000000000000000197bfd0342bbc8bee2beced2f173e1a87be576379b343e93232d6cef98d84b1d696e5612ff283ce2cfdccb2cfb65fa0c00000000000000000000000000000000184e811f55e6f9d84d77d2f79102fd7ea7422f4759df5bf7f6331d550245e3f1bcf6a30e3b29110d85e0ca16f9f6ae7a000000000000000000000000000000000f10e1eb3c1e53d2ad9cf2d398b2dc22c5842fab0a74b174f691a7e914975da3564d835cd7d2982815b8ac57f507348f000000000000000000000000000000000767d1c453890f1b9110fda82f5815c27281aba3f026ee868e4176a0654feea41a96575e0c4d58a14dbfbcc05b5010b10000000000000000000000000000000000000000000000000000000000000002", + "ExpectedError": "g2 point is not on correct subgroup", + "Name": "bls_g2mul_g2_not_in_correct_subgroup" + } +] \ No newline at end of file diff --git a/tests/prague/eip2537_bls_12_381_precompiles/vectors/fail-multiexp_G1_bls.json b/tests/prague/eip2537_bls_12_381_precompiles/vectors/fail-multiexp_G1_bls.json new file mode 100644 index 0000000000..976f28c480 --- /dev/null +++ b/tests/prague/eip2537_bls_12_381_precompiles/vectors/fail-multiexp_G1_bls.json @@ -0,0 +1,37 @@ +[ + { + "Input": "", + "ExpectedError": "invalid input length", + "Name": "bls_g1multiexp_empty_input" + }, + { + "Input": "00000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000112b98340eee2777cc3c14163dea3ec97977ac3dc5c70da32e6e87578f44912e902ccef9efe28d4a78b8999dfbca942600000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a210000000000000000000000000000000000000000000000000000000000000002", + "ExpectedError": "invalid input length", + "Name": "bls_g1multiexp_short_input" + }, + { + "Input": "000000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000112b98340eee2777cc3c14163dea3ec97977ac3dc5c70da32e6e87578f44912e902ccef9efe28d4a78b8999dfbca942600000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a210000000000000000000000000000000000000000000000000000000000000002", + "ExpectedError": "invalid input length", + "Name": "bls_g1multiexp_long_input" + }, + { + "Input": "0000000000000000000000000000000031f2e5916b17be2e71b10b4292f558e727dfd7d48af9cbc5087f0ce00dcca27c8b01e83eaace1aefb539f00adb2271660000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000112b98340eee2777cc3c14163dea3ec97977ac3dc5c70da32e6e87578f44912e902ccef9efe28d4a78b8999dfbca942600000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a210000000000000000000000000000000000000000000000000000000000000002", + "ExpectedError": "invalid fp.Element encoding", + "Name": "bls_g1multiexp_invalid_field_element" + }, + { + "Input": "1000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000112b98340eee2777cc3c14163dea3ec97977ac3dc5c70da32e6e87578f44912e902ccef9efe28d4a78b8999dfbca942600000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a210000000000000000000000000000000000000000000000000000000000000002", + "ExpectedError": "invalid field element top bytes", + "Name": "bls_g1multiexp_violate_top_bytes" + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb00000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a21000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000112b98340eee2777cc3c14163dea3ec97977ac3dc5c70da32e6e87578f44912e902ccef9efe28d4a78b8999dfbca942600000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a210000000000000000000000000000000000000000000000000000000000000002", + "ExpectedError": "invalid point: not on curve", + "Name": "bls_g1multiexp_point_not_on_curve" + }, + { + "Input": "000000000000000000000000000000000123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef00000000000000000000000000000000193fb7cedb32b2c3adc06ec11a96bc0d661869316f5e4a577a9f7c179593987beb4fb2ee424dbb2f5dd891e228b46c4a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000112b98340eee2777cc3c14163dea3ec97977ac3dc5c70da32e6e87578f44912e902ccef9efe28d4a78b8999dfbca942600000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a210000000000000000000000000000000000000000000000000000000000000002", + "ExpectedError": "g1 point is not on correct subgroup", + "Name": "bls_g1multiexp_g1_not_in_correct_subgroup" + } +] \ No newline at end of file diff --git a/tests/prague/eip2537_bls_12_381_precompiles/vectors/fail-multiexp_G2_bls.json b/tests/prague/eip2537_bls_12_381_precompiles/vectors/fail-multiexp_G2_bls.json new file mode 100644 index 0000000000..486138985b --- /dev/null +++ b/tests/prague/eip2537_bls_12_381_precompiles/vectors/fail-multiexp_G2_bls.json @@ -0,0 +1,37 @@ +[ + { + "Input": "", + "ExpectedError": "invalid input length", + "Name": "bls_g2multiexp_empty_input" + }, + { + "Input": "000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d8784510000000000000000000000000000000000000000000000000000000000000002", + "ExpectedError": "invalid input length", + "Name": "bls_g2multiexp_short_input" + }, + { + "Input": "0000000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d8784510000000000000000000000000000000000000000000000000000000000000002", + "ExpectedError": "invalid input length", + "Name": "bls_g2multiexp_long_input" + }, + { + "Input": "10000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d8784510000000000000000000000000000000000000000000000000000000000000002", + "ExpectedError": "invalid field element top bytes", + "Name": "bls_g2multiexp_violate_top_bytes" + }, + { + "Input": "000000000000000000000000000000001c4bb49d2a0ef12b7123acdd7110bd292b5bc659edc54dc21b81de057194c79b2a5803255959bbef8e7f56c8c12168630000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d8784510000000000000000000000000000000000000000000000000000000000000002", + "ExpectedError": "invalid fp.Element encoding", + "Name": "bls_g2multiexp_invalid_field_element" + }, + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb800000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d8784510000000000000000000000000000000000000000000000000000000000000002", + "ExpectedError": "invalid point: not on curve", + "Name": "bls_g2multiexp_point_not_on_curve" + }, + { + "Input": "00000000000000000000000000000000197bfd0342bbc8bee2beced2f173e1a87be576379b343e93232d6cef98d84b1d696e5612ff283ce2cfdccb2cfb65fa0c00000000000000000000000000000000184e811f55e6f9d84d77d2f79102fd7ea7422f4759df5bf7f6331d550245e3f1bcf6a30e3b29110d85e0ca16f9f6ae7a000000000000000000000000000000000f10e1eb3c1e53d2ad9cf2d398b2dc22c5842fab0a74b174f691a7e914975da3564d835cd7d2982815b8ac57f507348f000000000000000000000000000000000767d1c453890f1b9110fda82f5815c27281aba3f026ee868e4176a0654feea41a96575e0c4d58a14dbfbcc05b5010b1000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d8784510000000000000000000000000000000000000000000000000000000000000002", + "ExpectedError": "g2 point is not on correct subgroup", + "Name": "bls_pairing_g2_not_in_correct_subgroup" + } +] \ No newline at end of file diff --git a/tests/prague/eip2537_bls_12_381_precompiles/vectors/fail-pairing_check_bls.json b/tests/prague/eip2537_bls_12_381_precompiles/vectors/fail-pairing_check_bls.json new file mode 100644 index 0000000000..e14cb8e648 --- /dev/null +++ b/tests/prague/eip2537_bls_12_381_precompiles/vectors/fail-pairing_check_bls.json @@ -0,0 +1,47 @@ +[ + { + "Input": "", + "ExpectedError": "invalid input length", + "Name": "bls_pairing_empty_input" + }, + { + "Input": "00000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000d1b3cc2c7027888be51d9ef691d77bcb679afda66c73f17f9ee3837a55024f78c71363275a75d75d86bab79f74782aa0000000000000000000000000000000013fa4d4a0ad8b1ce186ed5061789213d993923066dddaf1040bc3ff59f825c78df74f2d75467e25e0f55f8a00fa030ed", + "ExpectedError": "invalid input length", + "Name": "bls_pairing_missing_data" + }, + { + "Input": "000000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000d1b3cc2c7027888be51d9ef691d77bcb679afda66c73f17f9ee3837a55024f78c71363275a75d75d86bab79f74782aa0000000000000000000000000000000013fa4d4a0ad8b1ce186ed5061789213d993923066dddaf1040bc3ff59f825c78df74f2d75467e25e0f55f8a00fa030ed", + "ExpectedError": "invalid input length", + "Name": "bls_pairing_extra_data" + }, + { + "Input": "1000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000d1b3cc2c7027888be51d9ef691d77bcb679afda66c73f17f9ee3837a55024f78c71363275a75d75d86bab79f74782aa0000000000000000000000000000000013fa4d4a0ad8b1ce186ed5061789213d993923066dddaf1040bc3ff59f825c78df74f2d75467e25e0f55f8a00fa030ed", + "ExpectedError": "invalid field element top bytes", + "Name": "bls_pairing_top_bytes" + }, + { + "Input": "0000000000000000000000000000000031f2e5916b17be2e71b10b4292f558e727dfd7d48af9cbc5087f0ce00dcca27c8b01e83eaace1aefb539f00adb2271660000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000d1b3cc2c7027888be51d9ef691d77bcb679afda66c73f17f9ee3837a55024f78c71363275a75d75d86bab79f74782aa0000000000000000000000000000000013fa4d4a0ad8b1ce186ed5061789213d993923066dddaf1040bc3ff59f825c78df74f2d75467e25e0f55f8a00fa030ed", + "ExpectedError": "invalid fp.Element encoding", + "Name": "bls_pairing_invalid_field_element" + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb00000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a2100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000d1b3cc2c7027888be51d9ef691d77bcb679afda66c73f17f9ee3837a55024f78c71363275a75d75d86bab79f74782aa0000000000000000000000000000000013fa4d4a0ad8b1ce186ed5061789213d993923066dddaf1040bc3ff59f825c78df74f2d75467e25e0f55f8a00fa030ed", + "ExpectedError": "invalid point: not on curve", + "Name": "bls_pairing_g1_not_on_curve" + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb800000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000d1b3cc2c7027888be51d9ef691d77bcb679afda66c73f17f9ee3837a55024f78c71363275a75d75d86bab79f74782aa0000000000000000000000000000000013fa4d4a0ad8b1ce186ed5061789213d993923066dddaf1040bc3ff59f825c78df74f2d75467e25e0f55f8a00fa030ed", + "ExpectedError": "invalid point: not on curve", + "Name": "bls_pairing_g2_not_on_curve" + }, + { + "Input": "000000000000000000000000000000000123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef00000000000000000000000000000000193fb7cedb32b2c3adc06ec11a96bc0d661869316f5e4a577a9f7c179593987beb4fb2ee424dbb2f5dd891e228b46c4a00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000d1b3cc2c7027888be51d9ef691d77bcb679afda66c73f17f9ee3837a55024f78c71363275a75d75d86bab79f74782aa0000000000000000000000000000000013fa4d4a0ad8b1ce186ed5061789213d993923066dddaf1040bc3ff59f825c78df74f2d75467e25e0f55f8a00fa030ed", + "ExpectedError": "g1 point is not on correct subgroup", + "Name": "bls_pairing_g1_not_in_correct_subgroup" + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000197bfd0342bbc8bee2beced2f173e1a87be576379b343e93232d6cef98d84b1d696e5612ff283ce2cfdccb2cfb65fa0c00000000000000000000000000000000184e811f55e6f9d84d77d2f79102fd7ea7422f4759df5bf7f6331d550245e3f1bcf6a30e3b29110d85e0ca16f9f6ae7a000000000000000000000000000000000f10e1eb3c1e53d2ad9cf2d398b2dc22c5842fab0a74b174f691a7e914975da3564d835cd7d2982815b8ac57f507348f000000000000000000000000000000000767d1c453890f1b9110fda82f5815c27281aba3f026ee868e4176a0654feea41a96575e0c4d58a14dbfbcc05b5010b10000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000d1b3cc2c7027888be51d9ef691d77bcb679afda66c73f17f9ee3837a55024f78c71363275a75d75d86bab79f74782aa0000000000000000000000000000000013fa4d4a0ad8b1ce186ed5061789213d993923066dddaf1040bc3ff59f825c78df74f2d75467e25e0f55f8a00fa030ed", + "ExpectedError": "g2 point is not on correct subgroup", + "Name": "bls_pairing_g2_not_in_correct_subgroup" + } +] \ No newline at end of file diff --git a/tests/prague/eip2537_bls_12_381_precompiles/vectors/map_fp2_to_G2_bls.json b/tests/prague/eip2537_bls_12_381_precompiles/vectors/map_fp2_to_G2_bls.json new file mode 100644 index 0000000000..23c50680ec --- /dev/null +++ b/tests/prague/eip2537_bls_12_381_precompiles/vectors/map_fp2_to_G2_bls.json @@ -0,0 +1,37 @@ +[ + { + "Input": "0000000000000000000000000000000007355d25caf6e7f2f0cb2812ca0e513bd026ed09dda65b177500fa31714e09ea0ded3a078b526bed3307f804d4b93b040000000000000000000000000000000002829ce3c021339ccb5caf3e187f6370e1e2a311dec9b75363117063ab2015603ff52c3d3b98f19c2f65575e99e8b78c", + "Name": "bls_g2map_", + "Expected": "0000000000000000000000000000000000e7f4568a82b4b7dc1f14c6aaa055edf51502319c723c4dc2688c7fe5944c213f510328082396515734b6612c4e7bb700000000000000000000000000000000126b855e9e69b1f691f816e48ac6977664d24d99f8724868a184186469ddfd4617367e94527d4b74fc86413483afb35b000000000000000000000000000000000caead0fd7b6176c01436833c79d305c78be307da5f6af6c133c47311def6ff1e0babf57a0fb5539fce7ee12407b0a42000000000000000000000000000000001498aadcf7ae2b345243e281ae076df6de84455d766ab6fcdaad71fab60abb2e8b980a440043cd305db09d283c895e3d", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000138879a9559e24cecee8697b8b4ad32cced053138ab913b99872772dc753a2967ed50aabc907937aefb2439ba06cc50c000000000000000000000000000000000a1ae7999ea9bab1dcc9ef8887a6cb6e8f1e22566015428d220b7eec90ffa70ad1f624018a9ad11e78d588bd3617f9f2", + "Name": "bls_g2map_616263", + "Expected": "00000000000000000000000000000000108ed59fd9fae381abfd1d6bce2fd2fa220990f0f837fa30e0f27914ed6e1454db0d1ee957b219f61da6ff8be0d6441f000000000000000000000000000000000296238ea82c6d4adb3c838ee3cb2346049c90b96d602d7bb1b469b905c9228be25c627bffee872def773d5b2a2eb57d00000000000000000000000000000000033f90f6057aadacae7963b0a0b379dd46750c1c94a6357c99b65f63b79e321ff50fe3053330911c56b6ceea08fee65600000000000000000000000000000000153606c417e59fb331b7ae6bce4fbf7c5190c33ce9402b5ebe2b70e44fca614f3f1382a3625ed5493843d0b0a652fc3f", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000018c16fe362b7dbdfa102e42bdfd3e2f4e6191d479437a59db4eb716986bf08ee1f42634db66bde97d6c16bbfd342b3b8000000000000000000000000000000000e37812ce1b146d998d5f92bdd5ada2a31bfd63dfe18311aa91637b5f279dd045763166aa1615e46a50d8d8f475f184e", + "Name": "bls_g2map_6162636465663031", + "Expected": "00000000000000000000000000000000038af300ef34c7759a6caaa4e69363cafeed218a1f207e93b2c70d91a1263d375d6730bd6b6509dcac3ba5b567e85bf3000000000000000000000000000000000da75be60fb6aa0e9e3143e40c42796edf15685cafe0279afd2a67c3dff1c82341f17effd402e4f1af240ea90f4b659b0000000000000000000000000000000019b148cbdf163cf0894f29660d2e7bfb2b68e37d54cc83fd4e6e62c020eaa48709302ef8e746736c0e19342cc1ce3df4000000000000000000000000000000000492f4fed741b073e5a82580f7c663f9b79e036b70ab3e51162359cec4e77c78086fe879b65ca7a47d34374c8315ac5e", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000008d4a0997b9d52fecf99427abb721f0fa779479963315fe21c6445250de7183e3f63bfdf86570da8929489e421d4ee950000000000000000000000000000000016cb4ccad91ec95aab070f22043916cd6a59c4ca94097f7f510043d48515526dc8eaaea27e586f09151ae613688d5a89", + "Name": "bls_g2map_713132385f717171", + "Expected": "000000000000000000000000000000000c5ae723be00e6c3f0efe184fdc0702b64588fe77dda152ab13099a3bacd3876767fa7bbad6d6fd90b3642e902b208f90000000000000000000000000000000012c8c05c1d5fc7bfa847f4d7d81e294e66b9a78bc9953990c358945e1f042eedafce608b67fdd3ab0cb2e6e263b9b1ad0000000000000000000000000000000004e77ddb3ede41b5ec4396b7421dd916efc68a358a0d7425bddd253547f2fb4830522358491827265dfc5bcc1928a5690000000000000000000000000000000011c624c56dbe154d759d021eec60fab3d8b852395a89de497e48504366feedd4662d023af447d66926a28076813dd646", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000003f80ce4ff0ca2f576d797a3660e3f65b274285c054feccc3215c879e2c0589d376e83ede13f93c32f05da0f68fd6a1000000000000000000000000000000000006488a837c5413746d868d1efb7232724da10eca410b07d8b505b9363bdccf0a1fc0029bad07d65b15ccfe6dd25e20d", + "Name": "bls_g2map_613531325f616161", + "Expected": "000000000000000000000000000000000ea4e7c33d43e17cc516a72f76437c4bf81d8f4eac69ac355d3bf9b71b8138d55dc10fd458be115afa798b55dac34be1000000000000000000000000000000001565c2f625032d232f13121d3cfb476f45275c303a037faa255f9da62000c2c864ea881e2bcddd111edc4a3c0da3e88d00000000000000000000000000000000043b6f5fe4e52c839148dc66f2b3751e69a0f6ebb3d056d6465d50d4108543ecd956e10fa1640dfd9bc0030cc2558d28000000000000000000000000000000000f8991d2a1ad662e7b6f58ab787947f1fa607fce12dde171bc17903b012091b657e15333e11701edcf5b63ba2a561247", + "Gas": 75000, + "NoBenchmark": false + } +] \ No newline at end of file diff --git a/tests/prague/eip2537_bls_12_381_precompiles/vectors/map_fp_to_G1_bls.json b/tests/prague/eip2537_bls_12_381_precompiles/vectors/map_fp_to_G1_bls.json new file mode 100644 index 0000000000..80ca454d82 --- /dev/null +++ b/tests/prague/eip2537_bls_12_381_precompiles/vectors/map_fp_to_G1_bls.json @@ -0,0 +1,37 @@ +[ + { + "Input": "00000000000000000000000000000000156c8a6a2c184569d69a76be144b5cdc5141d2d2ca4fe341f011e25e3969c55ad9e9b9ce2eb833c81a908e5fa4ac5f03", + "Name": "bls_g1map_", + "Expected": "00000000000000000000000000000000184bb665c37ff561a89ec2122dd343f20e0f4cbcaec84e3c3052ea81d1834e192c426074b02ed3dca4e7676ce4ce48ba0000000000000000000000000000000004407b8d35af4dacc809927071fc0405218f1401a6d15af775810e4e460064bcc9468beeba82fdc751be70476c888bf3", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000147e1ed29f06e4c5079b9d14fc89d2820d32419b990c1c7bb7dbea2a36a045124b31ffbde7c99329c05c559af1c6cc82", + "Name": "bls_g1map_616263", + "Expected": "00000000000000000000000000000000009769f3ab59bfd551d53a5f846b9984c59b97d6842b20a2c565baa167945e3d026a3755b6345df8ec7e6acb6868ae6d000000000000000000000000000000001532c00cf61aa3d0ce3e5aa20c3b531a2abd2c770a790a2613818303c6b830ffc0ecf6c357af3317b9575c567f11cd2c", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000004090815ad598a06897dd89bcda860f25837d54e897298ce31e6947378134d3761dc59a572154963e8c954919ecfa82d", + "Name": "bls_g1map_6162636465663031", + "Expected": "000000000000000000000000000000001974dbb8e6b5d20b84df7e625e2fbfecb2cdb5f77d5eae5fb2955e5ce7313cae8364bc2fff520a6c25619739c6bdcb6a0000000000000000000000000000000015f9897e11c6441eaa676de141c8d83c37aab8667173cbe1dfd6de74d11861b961dccebcd9d289ac633455dfcc7013a3", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000008dccd088ca55b8bfbc96fb50bb25c592faa867a8bb78d4e94a8cc2c92306190244532e91feba2b7fed977e3c3bb5a1f", + "Name": "bls_g1map_713132385f717171", + "Expected": "000000000000000000000000000000000a7a047c4a8397b3446450642c2ac64d7239b61872c9ae7a59707a8f4f950f101e766afe58223b3bff3a19a7f754027c000000000000000000000000000000001383aebba1e4327ccff7cf9912bda0dbc77de048b71ef8c8a81111d71dc33c5e3aa6edee9cf6f5fe525d50cc50b77cc9", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000dd824886d2123a96447f6c56e3a3fa992fbfefdba17b6673f9f630ff19e4d326529db37e1c1be43f905bf9202e0278d", + "Name": "bls_g1map_613531325f616161", + "Expected": "000000000000000000000000000000000e7a16a975904f131682edbb03d9560d3e48214c9986bd50417a77108d13dc957500edf96462a3d01e62dc6cd468ef11000000000000000000000000000000000ae89e677711d05c30a48d6d75e76ca9fb70fe06c6dd6ff988683d89ccde29ac7d46c53bb97a59b1901abf1db66052db", + "Gas": 5500, + "NoBenchmark": false + } +] \ No newline at end of file diff --git a/tests/prague/eip2537_bls_12_381_precompiles/vectors/mul_G1_bls.json b/tests/prague/eip2537_bls_12_381_precompiles/vectors/mul_G1_bls.json new file mode 100644 index 0000000000..72b62ce1e3 --- /dev/null +++ b/tests/prague/eip2537_bls_12_381_precompiles/vectors/mul_G1_bls.json @@ -0,0 +1,79 @@ +[ + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000002", + "Name": "bls_g1mul_(g1+g1=2*g1)", + "Expected": "000000000000000000000000000000000572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e00000000000000000000000000000000166a9d8cabc673a322fda673779d8e3822ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d28", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000112b98340eee2777cc3c14163dea3ec97977ac3dc5c70da32e6e87578f44912e902ccef9efe28d4a78b8999dfbca942600000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a210000000000000000000000000000000000000000000000000000000000000002", + "Name": "bls_g1mul_(p1+p1=2*p1)", + "Expected": "0000000000000000000000000000000015222cddbabdd764c4bee0b3720322a65ff4712c86fc4b1588d0c209210a0884fa9468e855d261c483091b2bf7de6a630000000000000000000000000000000009f9edb99bc3b75d7489735c98b16ab78b9386c5f7a1f76c7e96ac6eb5bbde30dbca31a74ec6e0f0b12229eecea33c39", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000001", + "Name": "bls_g1mul_(1*g1=g1)", + "Expected": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000112b98340eee2777cc3c14163dea3ec97977ac3dc5c70da32e6e87578f44912e902ccef9efe28d4a78b8999dfbca942600000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a210000000000000000000000000000000000000000000000000000000000000001", + "Name": "bls_g1mul_(1*p1=p1)", + "Expected": "00000000000000000000000000000000112b98340eee2777cc3c14163dea3ec97977ac3dc5c70da32e6e87578f44912e902ccef9efe28d4a78b8999dfbca942600000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a21", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000000", + "Name": "bls_g1mul_(0*g1=inf)", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000112b98340eee2777cc3c14163dea3ec97977ac3dc5c70da32e6e87578f44912e902ccef9efe28d4a78b8999dfbca942600000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a210000000000000000000000000000000000000000000000000000000000000000", + "Name": "bls_g1mul_(0*p1=inf)", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011", + "Name": "bls_g1mul_(x*inf=inf)", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1263dbd792f5b1be47ed85f8938c0f29586af0d3ac7b977f21c278fe1462040e3", + "Name": "bls_g1mul_random*g1", + "Expected": "000000000000000000000000000000000491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a0000000000000000000000000000000017cd7061575d3e8034fcea62adaa1a3bc38dca4b50e4c5c01d04dd78037c9cee914e17944ea99e7ad84278e5d49f36c4", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000112b98340eee2777cc3c14163dea3ec97977ac3dc5c70da32e6e87578f44912e902ccef9efe28d4a78b8999dfbca942600000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a21263dbd792f5b1be47ed85f8938c0f29586af0d3ac7b977f21c278fe1462040e3", + "Name": "bls_g1mul_random*p1", + "Expected": "0000000000000000000000000000000006ee9c9331228753bcb148d0ca8623447701bb0aa6eafb0340aa7f81543923474e00f2a225de65c62dd1d8303270220c0000000000000000000000000000000018dd7be47eb4e80985d7a0d2cc96c8b004250b36a5c3ec0217705d453d3ecc6d0d3d1588722da51b40728baba1e93804", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e19a2b64cc58f8992cb21237914262ca9ada6cb13dc7b7d3f11c278fe0462040e4", + "Name": "bls_g1mul_random*g1_unnormalized_scalar", + "Expected": "000000000000000000000000000000000491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a0000000000000000000000000000000017cd7061575d3e8034fcea62adaa1a3bc38dca4b50e4c5c01d04dd78037c9cee914e17944ea99e7ad84278e5d49f36c4", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000112b98340eee2777cc3c14163dea3ec97977ac3dc5c70da32e6e87578f44912e902ccef9efe28d4a78b8999dfbca942600000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a219a2b64cc58f8992cb21237914262ca9ada6cb13dc7b7d3f11c278fe0462040e4", + "Name": "bls_g1mul_random*p1_unnormalized_scalar", + "Expected": "0000000000000000000000000000000006ee9c9331228753bcb148d0ca8623447701bb0aa6eafb0340aa7f81543923474e00f2a225de65c62dd1d8303270220c0000000000000000000000000000000018dd7be47eb4e80985d7a0d2cc96c8b004250b36a5c3ec0217705d453d3ecc6d0d3d1588722da51b40728baba1e93804", + "Gas": 12000, + "NoBenchmark": false + } +] \ No newline at end of file diff --git a/tests/prague/eip2537_bls_12_381_precompiles/vectors/mul_G2_bls.json b/tests/prague/eip2537_bls_12_381_precompiles/vectors/mul_G2_bls.json new file mode 100644 index 0000000000..b7ddbcf1ed --- /dev/null +++ b/tests/prague/eip2537_bls_12_381_precompiles/vectors/mul_G2_bls.json @@ -0,0 +1,79 @@ +[ + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000002", + "Name": "bls_g2mul_(g2+g2=2*g2)", + "Expected": "000000000000000000000000000000001638533957d540a9d2370f17cc7ed5863bc0b995b8825e0ee1ea1e1e4d00dbae81f14b0bf3611b78c952aacab827a053000000000000000000000000000000000a4edef9c1ed7f729f520e47730a124fd70662a904ba1074728114d1031e1572c6c886f6b57ec72a6178288c47c33577000000000000000000000000000000000468fb440d82b0630aeb8dca2b5256789a66da69bf91009cbfe6bd221e47aa8ae88dece9764bf3bd999d95d71e4c9899000000000000000000000000000000000f6d4552fa65dd2638b361543f887136a43253d9c66c411697003f7a13c308f5422e1aa0a59c8967acdefd8b6e36ccf3", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d8784510000000000000000000000000000000000000000000000000000000000000002", + "Name": "bls_g2mul_(p2+p2=2*p2)", + "Expected": "000000000000000000000000000000000b76fcbb604082a4f2d19858a7befd6053fa181c5119a612dfec83832537f644e02454f2b70d40985ebb08042d1620d40000000000000000000000000000000019a4a02c0ae51365d964c73be7babb719db1c69e0ddbf9a8a335b5bed3b0a4b070d2d5df01d2da4a3f1e56aae2ec106d000000000000000000000000000000000d18322f821ac72d3ca92f92b000483cf5b7d9e5d06873a44071c4e7e81efd904f210208fe0b9b4824f01c65bc7e62080000000000000000000000000000000004e563d53609a2d1e216aaaee5fbc14ef460160db8d1fdc5e1bd4e8b54cd2f39abf6f925969fa405efb9e700b01c7085", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000001", + "Name": "bls_g2mul_(1*g2=g2)", + "Expected": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d8784510000000000000000000000000000000000000000000000000000000000000001", + "Name": "bls_g2mul_(1*p2=p2)", + "Expected": "00000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d878451", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000000", + "Name": "bls_g2mul_(0*g2=inf)", + "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d8784510000000000000000000000000000000000000000000000000000000000000000", + "Name": "bls_g2mul_(0*p2=inf)", + "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011", + "Name": "bls_g2mul_(x*inf=inf)", + "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be263dbd792f5b1be47ed85f8938c0f29586af0d3ac7b977f21c278fe1462040e3", + "Name": "bls_g2mul_random*g2", + "Expected": "0000000000000000000000000000000014856c22d8cdb2967c720e963eedc999e738373b14172f06fc915769d3cc5ab7ae0a1b9c38f48b5585fb09d4bd2733bb000000000000000000000000000000000c400b70f6f8cd35648f5c126cce5417f3be4d8eefbd42ceb4286a14df7e03135313fe5845e3a575faab3e8b949d248800000000000000000000000000000000149a0aacc34beba2beb2f2a19a440166e76e373194714f108e4ab1c3fd331e80f4e73e6b9ea65fe3ec96d7136de81544000000000000000000000000000000000e4622fef26bdb9b1e8ef6591a7cc99f5b73164500c1ee224b6a761e676b8799b09a3fd4fa7e242645cc1a34708285e4", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d878451263dbd792f5b1be47ed85f8938c0f29586af0d3ac7b977f21c278fe1462040e3", + "Name": "bls_g2mul_random*p2", + "Expected": "00000000000000000000000000000000036074dcbbd0e987531bfe0e45ddfbe09fd015665990ee0c352e8e403fe6af971d8f42141970d9ab14b4dd04874409e600000000000000000000000000000000019705637f24ba2f398f32c3a3e20d6a1cd0fd63e6f8f071cf603a8334f255744927e7bfdfdb18519e019c49ff6e914500000000000000000000000000000000008e74fcff4c4278c9accfb60809ed69bbcbe3d6213ef2304e078d15ec7d6decb4f462b24b8e7cc38cc11b6f2c9e0486000000000000000000000000000000001331d40100f38c1070afd832445881b47cf4d63894666d9907c85ac66604aab5ad329980938cc3c167ccc5b6bc1b8f30", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be9a2b64cc58f8992cb21237914262ca9ada6cb13dc7b7d3f11c278fe0462040e4", + "Name": "bls_g2mul_random*g2_unnormalized_scalar", + "Expected": "0000000000000000000000000000000014856c22d8cdb2967c720e963eedc999e738373b14172f06fc915769d3cc5ab7ae0a1b9c38f48b5585fb09d4bd2733bb000000000000000000000000000000000c400b70f6f8cd35648f5c126cce5417f3be4d8eefbd42ceb4286a14df7e03135313fe5845e3a575faab3e8b949d248800000000000000000000000000000000149a0aacc34beba2beb2f2a19a440166e76e373194714f108e4ab1c3fd331e80f4e73e6b9ea65fe3ec96d7136de81544000000000000000000000000000000000e4622fef26bdb9b1e8ef6591a7cc99f5b73164500c1ee224b6a761e676b8799b09a3fd4fa7e242645cc1a34708285e4", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d8784519a2b64cc58f8992cb21237914262ca9ada6cb13dc7b7d3f11c278fe0462040e4", + "Name": "bls_g2mul_random*p2_unnormalized_scalar", + "Expected": "00000000000000000000000000000000036074dcbbd0e987531bfe0e45ddfbe09fd015665990ee0c352e8e403fe6af971d8f42141970d9ab14b4dd04874409e600000000000000000000000000000000019705637f24ba2f398f32c3a3e20d6a1cd0fd63e6f8f071cf603a8334f255744927e7bfdfdb18519e019c49ff6e914500000000000000000000000000000000008e74fcff4c4278c9accfb60809ed69bbcbe3d6213ef2304e078d15ec7d6decb4f462b24b8e7cc38cc11b6f2c9e0486000000000000000000000000000000001331d40100f38c1070afd832445881b47cf4d63894666d9907c85ac66604aab5ad329980938cc3c167ccc5b6bc1b8f30", + "Gas": 45000, + "NoBenchmark": false + } +] \ No newline at end of file diff --git a/tests/prague/eip2537_bls_12_381_precompiles/vectors/multiexp_G1_bls.json b/tests/prague/eip2537_bls_12_381_precompiles/vectors/multiexp_G1_bls.json new file mode 100644 index 0000000000..0a1373782f --- /dev/null +++ b/tests/prague/eip2537_bls_12_381_precompiles/vectors/multiexp_G1_bls.json @@ -0,0 +1,79 @@ +[ + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000002", + "Name": "bls_g1multiexp_(g1+g1=2*g1)", + "Expected": "000000000000000000000000000000000572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e00000000000000000000000000000000166a9d8cabc673a322fda673779d8e3822ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d28", + "Gas": 14400, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000112b98340eee2777cc3c14163dea3ec97977ac3dc5c70da32e6e87578f44912e902ccef9efe28d4a78b8999dfbca942600000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a210000000000000000000000000000000000000000000000000000000000000002", + "Name": "bls_g1multiexp_(p1+p1=2*p1)", + "Expected": "0000000000000000000000000000000015222cddbabdd764c4bee0b3720322a65ff4712c86fc4b1588d0c209210a0884fa9468e855d261c483091b2bf7de6a630000000000000000000000000000000009f9edb99bc3b75d7489735c98b16ab78b9386c5f7a1f76c7e96ac6eb5bbde30dbca31a74ec6e0f0b12229eecea33c39", + "Gas": 14400, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000001", + "Name": "bls_g1multiexp_(1*g1=g1)", + "Expected": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1", + "Gas": 14400, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000112b98340eee2777cc3c14163dea3ec97977ac3dc5c70da32e6e87578f44912e902ccef9efe28d4a78b8999dfbca942600000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a210000000000000000000000000000000000000000000000000000000000000001", + "Name": "bls_g1multiexp_(1*p1=p1)", + "Expected": "00000000000000000000000000000000112b98340eee2777cc3c14163dea3ec97977ac3dc5c70da32e6e87578f44912e902ccef9efe28d4a78b8999dfbca942600000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a21", + "Gas": 14400, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000000", + "Name": "bls_g1multiexp_(0*g1=inf)", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Gas": 14400, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000112b98340eee2777cc3c14163dea3ec97977ac3dc5c70da32e6e87578f44912e902ccef9efe28d4a78b8999dfbca942600000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a210000000000000000000000000000000000000000000000000000000000000000", + "Name": "bls_g1multiexp_(0*p1=inf)", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Gas": 14400, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011", + "Name": "bls_g1multiexp_(x*inf=inf)", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Gas": 14400, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000000", + "Name": "bls_g1multiexp_(2g1+inf)", + "Expected": "000000000000000000000000000000000572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e00000000000000000000000000000000166a9d8cabc673a322fda673779d8e3822ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d28", + "Gas": 21312, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000000", + "Name": "bls_g1multiexp_(inf+inf)", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Gas": 21312, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000112b98340eee2777cc3c14163dea3ec97977ac3dc5c70da32e6e87578f44912e902ccef9efe28d4a78b8999dfbca942600000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a210000000000000000000000000000000000000000000000000000000000000002", + "Name": "bls_g1multiexp_(2g1+2p1)", + "Expected": "00000000000000000000000000000000148f92dced907361b4782ab542a75281d4b6f71f65c8abf94a5a9082388c64662d30fd6a01ced724feef3e284752038c0000000000000000000000000000000015c3634c3b67bc18e19150e12bfd8a1769306ed010f59be645a0823acb5b38f39e8e0d86e59b6353fdafc59ca971b769", + "Gas": 21312, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1263dbd792f5b1be47ed85f8938c0f29586af0d3ac7b977f21c278fe1462040e300000000000000000000000000000000112b98340eee2777cc3c14163dea3ec97977ac3dc5c70da32e6e87578f44912e902ccef9efe28d4a78b8999dfbca942600000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a2147b8192d77bf871b62e87859d653922725724a5c031afeabc60bcef5ff66513800000000000000000000000000000000184bb665c37ff561a89ec2122dd343f20e0f4cbcaec84e3c3052ea81d1834e192c426074b02ed3dca4e7676ce4ce48ba0000000000000000000000000000000004407b8d35af4dacc809927071fc0405218f1401a6d15af775810e4e460064bcc9468beeba82fdc751be70476c888bf3328388aff0d4a5b7dc9205abd374e7e98f3cd9f3418edb4eafda5fb16473d21600000000000000000000000000000000009769f3ab59bfd551d53a5f846b9984c59b97d6842b20a2c565baa167945e3d026a3755b6345df8ec7e6acb6868ae6d000000000000000000000000000000001532c00cf61aa3d0ce3e5aa20c3b531a2abd2c770a790a2613818303c6b830ffc0ecf6c357af3317b9575c567f11cd2c263dbd792f5b1be47ed85f8938c0f29586af0d3ac7b977f21c278fe1462040e2000000000000000000000000000000001974dbb8e6b5d20b84df7e625e2fbfecb2cdb5f77d5eae5fb2955e5ce7313cae8364bc2fff520a6c25619739c6bdcb6a0000000000000000000000000000000015f9897e11c6441eaa676de141c8d83c37aab8667173cbe1dfd6de74d11861b961dccebcd9d289ac633455dfcc7013a347b8192d77bf871b62e87859d653922725724a5c031afeabc60bcef5ff665131000000000000000000000000000000000a7a047c4a8397b3446450642c2ac64d7239b61872c9ae7a59707a8f4f950f101e766afe58223b3bff3a19a7f754027c000000000000000000000000000000001383aebba1e4327ccff7cf9912bda0dbc77de048b71ef8c8a81111d71dc33c5e3aa6edee9cf6f5fe525d50cc50b77cc9328388aff0d4a5b7dc9205abd374e7e98f3cd9f3418edb4eafda5fb16473d211000000000000000000000000000000000e7a16a975904f131682edbb03d9560d3e48214c9986bd50417a77108d13dc957500edf96462a3d01e62dc6cd468ef11000000000000000000000000000000000ae89e677711d05c30a48d6d75e76ca9fb70fe06c6dd6ff988683d89ccde29ac7d46c53bb97a59b1901abf1db66052db55b53c4669f19f0fc7431929bc0363d7d8fb432435fcde2635fdba334424e9f5", + "Name": "bls_g1multiexp_multiple", + "Expected": "00000000000000000000000000000000053fbdb09b6b5faa08bfe7b7069454247ad4d8bd57e90e2d2ebaa04003dcf110aa83072c07f480ab2107cca2ccff6091000000000000000000000000000000001654537b7c96fe64d13906066679c3d45808cb666452b55d1b909c230cc4b423c3f932c58754b9b762dc49fcc825522c", + "Gas": 42000, + "NoBenchmark": false + } +] \ No newline at end of file diff --git a/tests/prague/eip2537_bls_12_381_precompiles/vectors/multiexp_G2_bls.json b/tests/prague/eip2537_bls_12_381_precompiles/vectors/multiexp_G2_bls.json new file mode 100644 index 0000000000..bcfa1bbe68 --- /dev/null +++ b/tests/prague/eip2537_bls_12_381_precompiles/vectors/multiexp_G2_bls.json @@ -0,0 +1,86 @@ +[ + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000002", + "Name": "bls_g2multiexp_(g2+g2=2*g2)", + "Expected": "000000000000000000000000000000001638533957d540a9d2370f17cc7ed5863bc0b995b8825e0ee1ea1e1e4d00dbae81f14b0bf3611b78c952aacab827a053000000000000000000000000000000000a4edef9c1ed7f729f520e47730a124fd70662a904ba1074728114d1031e1572c6c886f6b57ec72a6178288c47c33577000000000000000000000000000000000468fb440d82b0630aeb8dca2b5256789a66da69bf91009cbfe6bd221e47aa8ae88dece9764bf3bd999d95d71e4c9899000000000000000000000000000000000f6d4552fa65dd2638b361543f887136a43253d9c66c411697003f7a13c308f5422e1aa0a59c8967acdefd8b6e36ccf3", + "Gas": 54000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d8784510000000000000000000000000000000000000000000000000000000000000002", + "Name": "bls_g2multiexp_(p2+p2=2*p2)", + "Expected": "000000000000000000000000000000000b76fcbb604082a4f2d19858a7befd6053fa181c5119a612dfec83832537f644e02454f2b70d40985ebb08042d1620d40000000000000000000000000000000019a4a02c0ae51365d964c73be7babb719db1c69e0ddbf9a8a335b5bed3b0a4b070d2d5df01d2da4a3f1e56aae2ec106d000000000000000000000000000000000d18322f821ac72d3ca92f92b000483cf5b7d9e5d06873a44071c4e7e81efd904f210208fe0b9b4824f01c65bc7e62080000000000000000000000000000000004e563d53609a2d1e216aaaee5fbc14ef460160db8d1fdc5e1bd4e8b54cd2f39abf6f925969fa405efb9e700b01c7085", + "Gas": 54000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000001", + "Name": "bls_g2multiexp_(1*g2=g2)", + "Expected": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be", + "Gas": 54000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d8784510000000000000000000000000000000000000000000000000000000000000001", + "Name": "bls_g2multiexp_(1*p2=p2)", + "Expected": "00000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d878451", + "Gas": 54000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000000", + "Name": "bls_g2multiexp_(0*g2=inf)", + "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Gas": 54000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d8784510000000000000000000000000000000000000000000000000000000000000000", + "Name": "bls_g2multiexp_(0*p2=inf)", + "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Gas": 54000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011", + "Name": "bls_g2multiexp_(x*inf=inf)", + "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Gas": 54000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002", + "Name": "bls_g2multiexp_(2g2+inf)", + "Expected": "000000000000000000000000000000001638533957d540a9d2370f17cc7ed5863bc0b995b8825e0ee1ea1e1e4d00dbae81f14b0bf3611b78c952aacab827a053000000000000000000000000000000000a4edef9c1ed7f729f520e47730a124fd70662a904ba1074728114d1031e1572c6c886f6b57ec72a6178288c47c33577000000000000000000000000000000000468fb440d82b0630aeb8dca2b5256789a66da69bf91009cbfe6bd221e47aa8ae88dece9764bf3bd999d95d71e4c9899000000000000000000000000000000000f6d4552fa65dd2638b361543f887136a43253d9c66c411697003f7a13c308f5422e1aa0a59c8967acdefd8b6e36ccf3", + "Gas": 79920, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d8784510000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002", + "Name": "bls_g2multiexp_(2p2+inf)", + "Expected": "000000000000000000000000000000000b76fcbb604082a4f2d19858a7befd6053fa181c5119a612dfec83832537f644e02454f2b70d40985ebb08042d1620d40000000000000000000000000000000019a4a02c0ae51365d964c73be7babb719db1c69e0ddbf9a8a335b5bed3b0a4b070d2d5df01d2da4a3f1e56aae2ec106d000000000000000000000000000000000d18322f821ac72d3ca92f92b000483cf5b7d9e5d06873a44071c4e7e81efd904f210208fe0b9b4824f01c65bc7e62080000000000000000000000000000000004e563d53609a2d1e216aaaee5fbc14ef460160db8d1fdc5e1bd4e8b54cd2f39abf6f925969fa405efb9e700b01c7085", + "Gas": 79920, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d878451000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d8784510000000000000000000000000000000000000000000000000000000000000000", + "Name": "bls_g1multiexp_(inf+inf)", + "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Gas": 79920, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d8784510000000000000000000000000000000000000000000000000000000000000002", + "Name": "bls_g2multiexp_(2g2+2p2)", + "Expected": "00000000000000000000000000000000009cc9ed6635623ba19b340cbc1b0eb05c3a58770623986bb7e041645175b0a38d663d929afb9a949f7524656043bccc000000000000000000000000000000000c0fb19d3f083fd5641d22a861a11979da258003f888c59c33005cb4a2df4df9e5a2868832063ac289dfa3e997f21f8a00000000000000000000000000000000168bf7d87cef37cf1707849e0a6708cb856846f5392d205ae7418dd94d94ef6c8aa5b424af2e99d957567654b9dae1d90000000000000000000000000000000017e0fa3c3b2665d52c26c7d4cea9f35443f4f9007840384163d3aa3c7d4d18b21b65ff4380cf3f3b48e94b5eecb221dd", + "Gas": 79920, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be263dbd792f5b1be47ed85f8938c0f29586af0d3ac7b977f21c278fe1462040e300000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d87845147b8192d77bf871b62e87859d653922725724a5c031afeabc60bcef5ff66513800000000000000000000000000000000108ed59fd9fae381abfd1d6bce2fd2fa220990f0f837fa30e0f27914ed6e1454db0d1ee957b219f61da6ff8be0d6441f000000000000000000000000000000000296238ea82c6d4adb3c838ee3cb2346049c90b96d602d7bb1b469b905c9228be25c627bffee872def773d5b2a2eb57d00000000000000000000000000000000033f90f6057aadacae7963b0a0b379dd46750c1c94a6357c99b65f63b79e321ff50fe3053330911c56b6ceea08fee65600000000000000000000000000000000153606c417e59fb331b7ae6bce4fbf7c5190c33ce9402b5ebe2b70e44fca614f3f1382a3625ed5493843d0b0a652fc3f328388aff0d4a5b7dc9205abd374e7e98f3cd9f3418edb4eafda5fb16473d21600000000000000000000000000000000038af300ef34c7759a6caaa4e69363cafeed218a1f207e93b2c70d91a1263d375d6730bd6b6509dcac3ba5b567e85bf3000000000000000000000000000000000da75be60fb6aa0e9e3143e40c42796edf15685cafe0279afd2a67c3dff1c82341f17effd402e4f1af240ea90f4b659b0000000000000000000000000000000019b148cbdf163cf0894f29660d2e7bfb2b68e37d54cc83fd4e6e62c020eaa48709302ef8e746736c0e19342cc1ce3df4000000000000000000000000000000000492f4fed741b073e5a82580f7c663f9b79e036b70ab3e51162359cec4e77c78086fe879b65ca7a47d34374c8315ac5e263dbd792f5b1be47ed85f8938c0f29586af0d3ac7b977f21c278fe1462040e2000000000000000000000000000000000c5ae723be00e6c3f0efe184fdc0702b64588fe77dda152ab13099a3bacd3876767fa7bbad6d6fd90b3642e902b208f90000000000000000000000000000000012c8c05c1d5fc7bfa847f4d7d81e294e66b9a78bc9953990c358945e1f042eedafce608b67fdd3ab0cb2e6e263b9b1ad0000000000000000000000000000000004e77ddb3ede41b5ec4396b7421dd916efc68a358a0d7425bddd253547f2fb4830522358491827265dfc5bcc1928a5690000000000000000000000000000000011c624c56dbe154d759d021eec60fab3d8b852395a89de497e48504366feedd4662d023af447d66926a28076813dd64647b8192d77bf871b62e87859d653922725724a5c031afeabc60bcef5ff665131000000000000000000000000000000000ea4e7c33d43e17cc516a72f76437c4bf81d8f4eac69ac355d3bf9b71b8138d55dc10fd458be115afa798b55dac34be1000000000000000000000000000000001565c2f625032d232f13121d3cfb476f45275c303a037faa255f9da62000c2c864ea881e2bcddd111edc4a3c0da3e88d00000000000000000000000000000000043b6f5fe4e52c839148dc66f2b3751e69a0f6ebb3d056d6465d50d4108543ecd956e10fa1640dfd9bc0030cc2558d28000000000000000000000000000000000f8991d2a1ad662e7b6f58ab787947f1fa607fce12dde171bc17903b012091b657e15333e11701edcf5b63ba2a561247328388aff0d4a5b7dc9205abd374e7e98f3cd9f3418edb4eafda5fb16473d211", + "Name": "bls_g2multiexp_multiple", + "Expected": "0000000000000000000000000000000016cf5fd2c2f1b2e01cc48a6d03e8e6d7f3ad754d6c7d4000f806c18c28d8d559cf529dd159c74946a7713d1906894718000000000000000000000000000000000628d42142df8d620d1f3709ac01f382ba950eaf14c12863885af5838067deec4bb363ffda427fcbdd2b8ec6cc5784ae0000000000000000000000000000000018168dec2441ef462e9a769c782f81acdc7fa49dffebb996764ba9fa96b9200ceb5edd9e96b33c383bd042b4e6af191a000000000000000000000000000000001065aaea2c4aa1d2bee7f1e82a2138ae7016dbbade8383ad912d81eca5fb260086238f95f8cef8f2f491969d4cefa2c3", + "Gas": 147690, + "NoBenchmark": false + } +] \ No newline at end of file diff --git a/tests/prague/eip2537_bls_12_381_precompiles/vectors/pairing_check_bls.json b/tests/prague/eip2537_bls_12_381_precompiles/vectors/pairing_check_bls.json new file mode 100644 index 0000000000..48b96ba0c1 --- /dev/null +++ b/tests/prague/eip2537_bls_12_381_precompiles/vectors/pairing_check_bls.json @@ -0,0 +1,44 @@ +[ + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be", + "Name": "bls_pairing_e(G1,0)=e(0,G2)", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Gas": 151000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be", + "Name": "bls_pairing_non-degeneracy", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Gas": 108000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be00000000000000000000000000000000112b98340eee2777cc3c14163dea3ec97977ac3dc5c70da32e6e87578f44912e902ccef9efe28d4a78b8999dfbca942600000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a2100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be000000000000000000000000000000000a40300ce2dec9888b60690e9a41d3004fda4886854573974fab73b046d3147ba5b7a5bde85279ffede1b45b3918d82d0000000000000000000000000000000006d3d887e9f53b9ec4eb6cedf5607226754b07c01ace7834f57f3e7315faefb739e59018e22c492006190fba4a87002500000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000d1b3cc2c7027888be51d9ef691d77bcb679afda66c73f17f9ee3837a55024f78c71363275a75d75d86bab79f74782aa0000000000000000000000000000000013fa4d4a0ad8b1ce186ed5061789213d993923066dddaf1040bc3ff59f825c78df74f2d75467e25e0f55f8a00fa030ed", + "Name": "bls_pairing_bilinearity", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Gas": 194000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000d1b3cc2c7027888be51d9ef691d77bcb679afda66c73f17f9ee3837a55024f78c71363275a75d75d86bab79f74782aa0000000000000000000000000000000013fa4d4a0ad8b1ce186ed5061789213d993923066dddaf1040bc3ff59f825c78df74f2d75467e25e0f55f8a00fa030ed", + "Name": "bls_pairing_e(G1,-G2)=e(-G1,G2)", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Gas": 151000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a0000000000000000000000000000000017cd7061575d3e8034fcea62adaa1a3bc38dca4b50e4c5c01d04dd78037c9cee914e17944ea99e7ad84278e5d49f36c4000000000000000000000000000000000bc2357c6782bbb6a078d9e171fc7a81f7bd8ca73eb485e76317359908bb09bd372fd362a637512a9d48019b383e54890000000000000000000000000000000004b8f49c3bac0247a09487049492b0ed99cf90c56263141daa35f011330d3ced3f3ad78d252c51a3bb42fc7d8f182594000000000000000000000000000000000982d17b17404ac198a0ff5f2dffa56a328d95ec4732d9cca9da420ec7cf716dc63d56d0f5179a8b1ec71fe0328fe88200000000000000000000000000000000147c92cb19e43943bb20c5360a6c4347411eb8ffb3d6f19cc428a8dc0cb3fd1eb3ad02b1c21e21c78f65a7691ee63de90000000000000000000000000000000016cae74dc6523e5273dbd2d9d25c53f1e2c453e6d9ba3f605021cfb514fa0bdf721b05f2200f32591d733e739fabf438000000000000000000000000000000001405df65fb71b738510b3a2fc31c33ef3d884ccc84efb1017341a368bf40727b7ad8cdc8e3fd6b0eb94102488c5cb77000000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000d1b3cc2c7027888be51d9ef691d77bcb679afda66c73f17f9ee3837a55024f78c71363275a75d75d86bab79f74782aa0000000000000000000000000000000013fa4d4a0ad8b1ce186ed5061789213d993923066dddaf1040bc3ff59f825c78df74f2d75467e25e0f55f8a00fa030ed", + "Name": "bls_pairing_e(aG1,bG2)=e(abG1,G2)", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Gas": 151000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a0000000000000000000000000000000017cd7061575d3e8034fcea62adaa1a3bc38dca4b50e4c5c01d04dd78037c9cee914e17944ea99e7ad84278e5d49f36c4000000000000000000000000000000000bc2357c6782bbb6a078d9e171fc7a81f7bd8ca73eb485e76317359908bb09bd372fd362a637512a9d48019b383e54890000000000000000000000000000000004b8f49c3bac0247a09487049492b0ed99cf90c56263141daa35f011330d3ced3f3ad78d252c51a3bb42fc7d8f182594000000000000000000000000000000000982d17b17404ac198a0ff5f2dffa56a328d95ec4732d9cca9da420ec7cf716dc63d56d0f5179a8b1ec71fe0328fe88200000000000000000000000000000000147c92cb19e43943bb20c5360a6c4347411eb8ffb3d6f19cc428a8dc0cb3fd1eb3ad02b1c21e21c78f65a7691ee63de90000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb00000000000000000000000000000000114d1d6855d545a8aa7d76c8cf2e21f267816aef1db507c96655b9d5caac42364e6f38ba0ecb751bad54dcd6b939c2ca00000000000000000000000000000000166335679f3b3e2617b70c22c48e820e2c6a35149c4f96293035c1494a1ce4591f7a44bce94e9d76def50a71c9e7fa41000000000000000000000000000000000ef11c636091748476331159c8259c064da712ffec033c89299384b4c11b801893026726d992aacdc8e0a28db1a3ab82000000000000000000000000000000000fd8d4944030f480f44ce0d2d4fb67ff6264d30a0f3193cc218b062e5114cf9e4ce847489f7be94b0d4a9fc0c550fdc60000000000000000000000000000000000edba2c166be3d673ea77016163ae5cdf7b3c9bd480e733eb5c08a5f1c798793d339cb503005f5a9e586ea5aabf9695", + "Name": "bls_pairing_e(aG1,bG2)=e(G1,abG2)", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Gas": 151000, + "NoBenchmark": false + } +] \ No newline at end of file diff --git a/tests/prague/eip2537_bls_12_381_precompiles/vectors/test-vectors.md b/tests/prague/eip2537_bls_12_381_precompiles/vectors/test-vectors.md new file mode 100644 index 0000000000..3b3387225b --- /dev/null +++ b/tests/prague/eip2537_bls_12_381_precompiles/vectors/test-vectors.md @@ -0,0 +1,3 @@ +# Test Vectors for EIP-2537 - Precompile for BLS12-381 curve operations + +These test vectors are derived from [BLS 12-381 tests](https://github.com/ethereum/bls12-381-tests/tree/eip-2537) \ No newline at end of file diff --git a/tests/prague/eip2935_historical_block_hashes_from_state/__init__.py b/tests/prague/eip2935_historical_block_hashes_from_state/__init__.py new file mode 100644 index 0000000000..276289784b --- /dev/null +++ b/tests/prague/eip2935_historical_block_hashes_from_state/__init__.py @@ -0,0 +1,3 @@ +""" +Cross-client EIP-2935 Tests +""" diff --git a/tests/prague/eip2935_historical_block_hashes_from_state/spec.py b/tests/prague/eip2935_historical_block_hashes_from_state/spec.py new file mode 100644 index 0000000000..0757c98a23 --- /dev/null +++ b/tests/prague/eip2935_historical_block_hashes_from_state/spec.py @@ -0,0 +1,29 @@ +""" +Defines EIP-2935 specification constants and functions. +""" +from dataclasses import dataclass + + +@dataclass(frozen=True) +class ReferenceSpec: + """ + Defines the reference spec version and git path. + """ + + git_path: str + version: str + + +ref_spec_2935 = ReferenceSpec("EIPS/eip-2935.md", "3ab311ccd6029c080fb2a8b9615d493dfc093377") + + +@dataclass(frozen=True) +class Spec: + """ + Parameters from the EIP-2935 specifications as defined at + https://eips.ethereum.org/EIPS/eip-2935 + """ + + HISTORY_STORAGE_ADDRESS = 0x25A219378DAD9B3503C8268C9CA836A52427A4FB + HISTORY_SERVE_WINDOW = 8192 + BLOCKHASH_OLD_WINDOW = 256 diff --git a/tests/prague/eip2935_historical_block_hashes_from_state/test_block_hashes.py b/tests/prague/eip2935_historical_block_hashes_from_state/test_block_hashes.py new file mode 100644 index 0000000000..4872ce7594 --- /dev/null +++ b/tests/prague/eip2935_historical_block_hashes_from_state/test_block_hashes.py @@ -0,0 +1,200 @@ +""" +abstract: Tests [EIP-2935: Serve historical block hashes from state](https://eips.ethereum.org/EIPS/eip-2935) + Test [EIP-2935: Serve historical block hashes from state](https://eips.ethereum.org/EIPS/eip-2935) +""" # noqa: E501 + +from itertools import count +from typing import Dict, List + +import pytest + +from ethereum_test_tools import Account, Address, Block, BlockchainTestFiller, Environment +from ethereum_test_tools import Opcodes as Op +from ethereum_test_tools import Storage, TestAddress, Transaction + +from .spec import Spec, ref_spec_2935 + +REFERENCE_SPEC_GIT_PATH = ref_spec_2935.git_path +REFERENCE_SPEC_VERSION = ref_spec_2935.version + +FORK_TIMESTAMP = 15_000 + + +def generate_block_check_code( + block_number: int | None, + populated_blockhash: bool, + populated_contract: bool, + storage: Storage, + check_contract_first: bool = False, +) -> bytes: + """ + Generate EVM code to check that the blockhashes are correctly stored in the state. + + Args: + block_number (int | None): The block number to check (or None to return empty code). + populated_blockhash (bool): Whether the blockhash should be populated. + populated_contract (bool): Whether the contract should be populated. + storage (Storage): The storage object to use. + check_contract_first (bool): Whether to check the contract first, for slot warming checks. + """ + if block_number is None: + # No block number to check + return b"" + + blockhash_key = storage.store_next(not populated_blockhash) + contract_key = storage.store_next(not populated_contract) + + check_blockhash = Op.SSTORE(blockhash_key, Op.ISZERO(Op.BLOCKHASH(block_number))) + check_contract = ( + Op.MSTORE(0, block_number) + + Op.POP(Op.CALL(Op.GAS, Spec.HISTORY_STORAGE_ADDRESS, 0, 0, 32, 0, 32)) + + Op.SSTORE(contract_key, Op.ISZERO(Op.MLOAD(0))) + ) + + if check_contract_first: + code = check_contract + check_blockhash + else: + code = check_blockhash + check_contract + + if populated_contract and populated_blockhash: + # Both values must be equal + code += Op.SSTORE(storage.store_next(True), Op.EQ(Op.MLOAD(0), Op.BLOCKHASH(block_number))) + + return code + + +@pytest.mark.parametrize( + "blocks_before_fork", + [ + pytest.param(1, id="fork_at_1"), + pytest.param(Spec.BLOCKHASH_OLD_WINDOW, id="fork_at_BLOCKHASH_OLD_WINDOW"), + pytest.param( + Spec.BLOCKHASH_OLD_WINDOW + 1, + id="fork_at_BLOCKHASH_OLD_WINDOW_plus_1", + ), + pytest.param( + Spec.BLOCKHASH_OLD_WINDOW + 2, + id="fork_at_BLOCKHASH_OLD_WINDOW_plus_2", + ), + pytest.param( + Spec.HISTORY_SERVE_WINDOW + 1, + id="fork_at_HISTORY_SERVE_WINDOW_plus_1", + marks=pytest.mark.slow, + ), + ], +) +@pytest.mark.valid_at_transition_to("Prague") +def test_block_hashes_history_at_transition( + blockchain_test: BlockchainTestFiller, + blocks_before_fork: int, +): + """ + Test the fork transition and that the block hashes of previous blocks, even blocks + before the fork, are included in the state at the moment of the transition. + """ + # Fork happens at timestamp 15_000, and genesis counts as a block before fork. + blocks: List[Block] = [] + assert blocks_before_fork >= 1 and blocks_before_fork < FORK_TIMESTAMP + + pre = {TestAddress: Account(balance=10_000_000_000)} + post: Dict[Address, Account] = {} + tx_nonce = count(0) + + current_code_address = 0x10000 + for i in range(1, blocks_before_fork): + txs: List[Transaction] = [] + if i == blocks_before_fork - 1: + # On the last block before the fork, BLOCKHASH must return values for the last 256 + # blocks but not for the blocks before that. + # And HISTORY_STORAGE_ADDRESS should be empty. + code = b"" + storage = Storage() + + # Check the last block before the window + code += generate_block_check_code( + block_number=( + i - Spec.BLOCKHASH_OLD_WINDOW - 1 + if i > Spec.BLOCKHASH_OLD_WINDOW + else None # Chain not long enough, no block to check + ), + populated_blockhash=False, + populated_contract=False, + storage=storage, + ) + + # Check the first block inside the window + code += generate_block_check_code( + block_number=( + i - Spec.BLOCKHASH_OLD_WINDOW + if i > Spec.BLOCKHASH_OLD_WINDOW + else 0 # Entire chain is inside the window, check genesis + ), + populated_blockhash=True, + populated_contract=False, + storage=storage, + ) + + txs.append( + Transaction( + to=current_code_address, + gas_limit=10_000_000, + nonce=next(tx_nonce), + ) + ) + pre[Address(current_code_address)] = Account(code=code, nonce=1) + post[Address(current_code_address)] = Account(storage=storage) + current_code_address += 0x100 + blocks.append(Block(timestamp=i, txs=txs)) + + # Add the fork block + current_block_number = len(blocks) + 1 + txs = [] + # On the block after the fork, BLOCKHASH must return values for the last + # Spec.HISTORY_SERVE_WINDOW blocks. + # And HISTORY_STORAGE_ADDRESS should be also serve the same values. + code = b"" + storage = Storage() + + # Check the last block before the window + code += generate_block_check_code( + block_number=( + current_block_number - Spec.HISTORY_SERVE_WINDOW - 1 + if current_block_number > Spec.HISTORY_SERVE_WINDOW + else None # Chain not long enough, no block to check + ), + populated_blockhash=False, + populated_contract=False, + storage=storage, + ) + + # Check the first block inside the window + code += generate_block_check_code( + block_number=( + current_block_number - Spec.HISTORY_SERVE_WINDOW + if current_block_number > Spec.HISTORY_SERVE_WINDOW + else 0 # Entire chain is inside the window, check genesis + ), + populated_blockhash=True, + populated_contract=True, + storage=storage, + ) + + txs.append( + Transaction( + to=current_code_address, + gas_limit=10_000_000, + nonce=next(tx_nonce), + ) + ) + pre[Address(current_code_address)] = Account(code=code, nonce=1) + post[Address(current_code_address)] = Account(storage=storage) + current_code_address += 0x100 + + blocks.append(Block(timestamp=FORK_TIMESTAMP, txs=txs)) + + blockchain_test( + genesis_environment=Environment(), + pre=pre, + blocks=blocks, + post=post, + ) diff --git a/tests/prague/eip6110_deposits/__init__.py b/tests/prague/eip6110_deposits/__init__.py new file mode 100644 index 0000000000..d1038f50fc --- /dev/null +++ b/tests/prague/eip6110_deposits/__init__.py @@ -0,0 +1,3 @@ +""" +Cross-client EIP-6110 Tests +""" diff --git a/tests/prague/eip6110_deposits/conftest.py b/tests/prague/eip6110_deposits/conftest.py new file mode 100644 index 0000000000..0aa8f6c4c8 --- /dev/null +++ b/tests/prague/eip6110_deposits/conftest.py @@ -0,0 +1,85 @@ +""" +Fixtures for the EIP-6110 deposit tests. +""" +from typing import Dict, List + +import pytest + +from ethereum_test_tools import Account, Address, Block, BlockException, Header, Transaction + +from .helpers import DepositInteractionBase, DepositRequest + + +@pytest.fixture +def pre(requests: List[DepositInteractionBase]) -> Dict[Address, Account]: + """ + Initial state of the accounts. Every deposit transaction defines their own pre-state + requirements, and this fixture aggregates them all. + """ + pre: Dict[Address, Account] = {} + for d in requests: + d.update_pre(pre) + return pre + + +@pytest.fixture +def txs( + requests: List[DepositInteractionBase], +) -> List[Transaction]: + """List of transactions to include in the block.""" + address_nonce: Dict[Address, int] = {} + txs = [] + for r in requests: + nonce = 0 + if r.sender_account.address in address_nonce: + nonce = address_nonce[r.sender_account.address] + txs.append(r.transaction(nonce)) + address_nonce[r.sender_account.address] = nonce + 1 + return txs + + +@pytest.fixture +def block_body_override_requests() -> List[DepositRequest] | None: + """List of requests that overwrite the requests in the header. None by default.""" + return None + + +@pytest.fixture +def exception() -> BlockException | None: + """Block exception expected by the tests. None by default.""" + return None + + +@pytest.fixture +def included_requests( + requests: List[DepositInteractionBase], +) -> List[DepositRequest]: + """ + Return the list of deposit requests that should be included in each block. + """ + valid_requests: List[DepositRequest] = [] + + for d in requests: + valid_requests += d.valid_requests(10**18) + + return valid_requests + + +@pytest.fixture +def blocks( + included_requests: List[DepositRequest], + block_body_override_requests: List[DepositRequest] | None, + txs: List[Transaction], + exception: BlockException | None, +) -> List[Block]: + """List of blocks that comprise the test.""" + return [ + Block( + txs=txs, + header_verify=Header( + requests_root=included_requests, + ), + requests=block_body_override_requests, + exception=exception, + ) + ] diff --git a/tests/prague/eip6110_deposits/helpers.py b/tests/prague/eip6110_deposits/helpers.py new file mode 100644 index 0000000000..cada0c6fb5 --- /dev/null +++ b/tests/prague/eip6110_deposits/helpers.py @@ -0,0 +1,305 @@ +""" +Helpers for the EIP-6110 deposit tests. +""" +from dataclasses import dataclass, field +from functools import cached_property +from hashlib import sha256 as sha256_hashlib +from typing import Callable, ClassVar, Dict, List + +from ethereum_test_tools import Account, Address +from ethereum_test_tools import DepositRequest as DepositRequestBase +from ethereum_test_tools import Hash +from ethereum_test_tools import Opcodes as Op +from ethereum_test_tools import ( + TestAddress, + TestAddress2, + TestPrivateKey, + TestPrivateKey2, + Transaction, +) + +from .spec import Spec + + +def sha256(*args: bytes) -> bytes: + """ + Returns the sha256 hash of the input. + """ + return sha256_hashlib(b"".join(args)).digest() + + +@dataclass +class SenderAccount: + """Test sender account descriptor.""" + + address: Address + key: str + + +TestAccount1 = SenderAccount(TestAddress, TestPrivateKey) +TestAccount2 = SenderAccount(TestAddress2, TestPrivateKey2) + + +class DepositRequest(DepositRequestBase): + """Deposit request descriptor.""" + + valid: bool = True + """ + Whether the deposit request is valid or not. + """ + gas_limit: int = 1_000_000 + """ + Gas limit for the call. + """ + calldata_modifier: Callable[[bytes], bytes] = lambda x: x + """ + Calldata modifier function. + """ + + interaction_contract_address: ClassVar[Address] = Address(Spec.DEPOSIT_CONTRACT_ADDRESS) + + @cached_property + def value(self) -> int: + """ + Returns the value of the deposit transaction. + """ + return self.amount * 10**9 + + @cached_property + def deposit_data_root(self) -> Hash: + """ + Returns the deposit data root of the deposit. + """ + pubkey_root = sha256(self.pubkey, b"\x00" * 16) + signature_root = sha256( + sha256(self.signature[:64]), sha256(self.signature[64:], b"\x00" * 32) + ) + pubkey_withdrawal_root = sha256(pubkey_root, self.withdrawal_credentials) + amount_bytes = (self.amount).to_bytes(32, byteorder="little") + amount_signature_root = sha256(amount_bytes, signature_root) + return Hash(sha256(pubkey_withdrawal_root, amount_signature_root)) + + @cached_property + def calldata(self) -> bytes: + """ + Returns the calldata needed to call the beacon chain deposit contract and make the deposit. + + deposit( + bytes calldata pubkey, + bytes calldata withdrawal_credentials, + bytes calldata signature, + bytes32 deposit_data_root + ) + """ + offset_length = 32 + pubkey_offset = offset_length * 3 + len(self.deposit_data_root) + withdrawal_offset = pubkey_offset + offset_length + len(self.pubkey) + signature_offset = withdrawal_offset + offset_length + len(self.withdrawal_credentials) + return self.calldata_modifier( + b"\x22\x89\x51\x18" + + pubkey_offset.to_bytes(offset_length, byteorder="big") + + withdrawal_offset.to_bytes(offset_length, byteorder="big") + + signature_offset.to_bytes(offset_length, byteorder="big") + + self.deposit_data_root + + len(self.pubkey).to_bytes(offset_length, byteorder="big") + + self.pubkey + + len(self.withdrawal_credentials).to_bytes(offset_length, byteorder="big") + + self.withdrawal_credentials + + len(self.signature).to_bytes(offset_length, byteorder="big") + + self.signature + ) + + +@dataclass(kw_only=True) +class DepositInteractionBase: + """ + Base class for all types of deposit transactions we want to test. + """ + + sender_balance: int = 32_000_000_000_000_000_000 * 100 + """ + Balance of the account that sends the transaction. + """ + sender_account: SenderAccount = field( + default_factory=lambda: SenderAccount(TestAddress, TestPrivateKey) + ) + """ + Account that sends the transaction. + """ + + def transaction(self, nonce: int) -> Transaction: + """Return a transaction for the deposit request.""" + raise NotImplementedError + + def update_pre(self, base_pre: Dict[Address, Account]): + """Return the pre-state of the account.""" + raise NotImplementedError + + def valid_requests(self, current_minimum_fee: int) -> List[DepositRequest]: + """Return the list of deposit requests that should be included in the block.""" + raise NotImplementedError + + +@dataclass(kw_only=True) +class DepositTransaction(DepositInteractionBase): + """Class used to describe a deposit originated from an externally owned account.""" + + request: DepositRequest + """ + Deposit request to be included in the block. + """ + + def transaction(self, nonce: int) -> Transaction: + """Return a transaction for the deposit request.""" + return Transaction( + nonce=nonce, + gas_limit=self.request.gas_limit, + gas_price=0x07, + to=self.request.interaction_contract_address, + value=self.request.value, + data=self.request.calldata, + secret_key=self.sender_account.key, + ) + + def update_pre(self, base_pre: Dict[Address, Account]): + """Return the pre-state of the account.""" + base_pre.update( + { + self.sender_account.address: Account(balance=self.sender_balance), + } + ) + + def valid_requests(self, current_minimum_fee: int) -> List[DepositRequest]: + """Return the list of deposit requests that should be included in the block.""" + return ( + [self.request] + if self.request.valid and self.request.value >= current_minimum_fee + else [] + ) + + +@dataclass(kw_only=True) +class DepositContract(DepositInteractionBase): + """Class used to describe a deposit originated from a contract.""" + + request: List[DepositRequest] | DepositRequest + """ + Deposit request or list of deposit requests to send from the contract. + """ + + tx_gas_limit: int = 1_000_000 + """ + Gas limit for the transaction. + """ + + contract_balance: int = 32_000_000_000_000_000_000 * 100 + """ + Balance of the contract that sends the deposit requests. + """ + contract_address: int = 0x200 + """ + Address of the contract that sends the deposit requests. + """ + + call_type: Op = field(default_factory=lambda: Op.CALL) + """ + Type of call to be made to the deposit contract. + """ + call_depth: int = 2 + """ + Frame depth of the beacon chain deposit contract when it executes the deposit requests. + """ + extra_code: bytes = b"" + """ + Extra code to be included in the contract that sends the deposit requests. + """ + + @property + def requests(self) -> List[DepositRequest]: + """Return the list of deposit requests.""" + if not isinstance(self.request, List): + return [self.request] + return self.request + + @property + def contract_code(self) -> bytes: + """Contract code used by the relay contract.""" + code = b"" + current_offset = 0 + for r in self.requests: + value_arg = [r.value] if self.call_type in (Op.CALL, Op.CALLCODE) else [] + code += Op.CALLDATACOPY(0, current_offset, len(r.calldata)) + Op.POP( + self.call_type( + Op.GAS if r.gas_limit == -1 else r.gas_limit, + r.interaction_contract_address, + *value_arg, + 0, + len(r.calldata), + 0, + 0, + ) + ) + current_offset += len(r.calldata) + return code + self.extra_code + + def transaction(self, nonce: int) -> Transaction: + """Return a transaction for the deposit request.""" + return Transaction( + nonce=nonce, + gas_limit=self.tx_gas_limit, + gas_price=0x07, + to=self.entry_address(), + value=0, + data=b"".join(r.calldata for r in self.requests), + secret_key=self.sender_account.key, + ) + + def entry_address(self) -> Address: + """Return the address of the contract entry point.""" + if self.call_depth == 2: + return Address(self.contract_address) + elif self.call_depth > 2: + return Address(self.contract_address + self.call_depth - 2) + raise ValueError("Invalid call depth") + + def extra_contracts(self) -> Dict[Address, Account]: + """Extra contracts used to simulate call depth.""" + if self.call_depth <= 2: + return {} + return { + Address(self.contract_address + i): Account( + balance=self.contract_balance, + code=Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + + Op.POP( + Op.CALL( + Op.GAS, + self.contract_address + i - 1, + 0, + 0, + Op.CALLDATASIZE, + 0, + 0, + ) + ), + nonce=1, + ) + for i in range(1, self.call_depth - 1) + } + + def update_pre(self, base_pre: Dict[Address, Account]): + """Return the pre-state of the account.""" + while Address(self.contract_address) in base_pre: + self.contract_address += 0x100 + base_pre.update( + { + self.sender_account.address: Account(balance=self.sender_balance), + Address(self.contract_address): Account( + balance=self.contract_balance, code=self.contract_code, nonce=1 + ), + } + ) + base_pre.update(self.extra_contracts()) + + def valid_requests(self, current_minimum_fee: int) -> List[DepositRequest]: + """Return the list of deposit requests that should be included in the block.""" + return [d for d in self.requests if d.valid and d.value >= current_minimum_fee] diff --git a/tests/prague/eip6110_deposits/spec.py b/tests/prague/eip6110_deposits/spec.py new file mode 100644 index 0000000000..167e6f7a59 --- /dev/null +++ b/tests/prague/eip6110_deposits/spec.py @@ -0,0 +1,27 @@ +""" +Defines EIP-6110 specification constants and functions. +""" +from dataclasses import dataclass + + +@dataclass(frozen=True) +class ReferenceSpec: + """ + Defines the reference spec version and git path. + """ + + git_path: str + version: str + + +ref_spec_6110 = ReferenceSpec("EIPS/eip-6110.md", "70a6ec21f62937caf665d98db2b41633e9287871") + + +@dataclass(frozen=True) +class Spec: + """ + Parameters from the EIP-6110 specifications as defined at + https://eips.ethereum.org/EIPS/eip-6110 + """ + + DEPOSIT_CONTRACT_ADDRESS = 0x00000000219AB540356CBB839CBE05303D7705FA diff --git a/tests/prague/eip6110_deposits/test_deposits.py b/tests/prague/eip6110_deposits/test_deposits.py new file mode 100644 index 0000000000..8b8f6eac17 --- /dev/null +++ b/tests/prague/eip6110_deposits/test_deposits.py @@ -0,0 +1,928 @@ +""" +abstract: Tests [EIP-6110: Supply validator deposits on chain](https://eips.ethereum.org/EIPS/eip-6110) + Test [EIP-6110: Supply validator deposits on chain](https://eips.ethereum.org/EIPS/eip-6110). +""" # noqa: E501 +from typing import Dict, List + +import pytest + +from ethereum_test_tools import ( + Account, + Address, + Block, + BlockchainTestFiller, + BlockException, + Environment, + Macros, +) +from ethereum_test_tools import Opcodes as Op + +from .helpers import ( + DepositContract, + DepositRequest, + DepositTransaction, + TestAccount1, + TestAccount2, +) +from .spec import ref_spec_6110 + +REFERENCE_SPEC_GIT_PATH = ref_spec_6110.git_path +REFERENCE_SPEC_VERSION = ref_spec_6110.version + +pytestmark = pytest.mark.valid_from("Prague") + + +@pytest.mark.parametrize( + "requests", + [ + pytest.param( + [ + DepositTransaction( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ), + ), + ], + id="single_deposit_from_eoa", + ), + pytest.param( + [ + DepositTransaction( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=120_000_000_000_000_000, + signature=0x03, + index=0x0, + ), + sender_balance=120_000_001_000_000_000 * 10**9, + ), + ], + id="single_deposit_from_eoa_huge_amount", + ), + pytest.param( + [ + DepositTransaction( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ), + ), + DepositTransaction( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x1, + ), + ), + ], + id="multiple_deposit_from_same_eoa", + ), + pytest.param( + [ + DepositTransaction( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=i, + ), + ) + for i in range(200) + ], + id="multiple_deposit_from_same_eoa_high_count", + ), + pytest.param( + [ + DepositTransaction( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ), + sender_account=TestAccount1, + ), + DepositTransaction( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x1, + ), + sender_account=TestAccount2, + ), + ], + id="multiple_deposit_from_different_eoa", + ), + pytest.param( + [ + DepositTransaction( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=999_999_999, + signature=0x03, + index=0x0, + ), + ), + DepositTransaction( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ), + ), + ], + id="multiple_deposit_from_same_eoa_first_reverts", + ), + pytest.param( + [ + DepositTransaction( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ), + ), + DepositTransaction( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=999_999_999, + signature=0x03, + index=0x0, + ), + ), + ], + id="multiple_deposit_from_same_eoa_last_reverts", + ), + pytest.param( + [ + DepositTransaction( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + # From traces, gas used by the first tx is 82,718 so reduce by one here + gas_limit=0x1431D, + valid=False, + ), + ), + DepositTransaction( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ), + ), + ], + id="multiple_deposit_from_same_eoa_first_oog", + ), + pytest.param( + [ + DepositTransaction( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ), + ), + DepositTransaction( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + # From traces, gas used by the second tx is 68,594 so reduce by one here + gas_limit=0x10BF1, + valid=False, + ), + ), + ], + id="multiple_deposit_from_same_eoa_last_oog", + ), + pytest.param( + [ + DepositTransaction( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + calldata_modifier=lambda _: b"", + valid=False, + ), + ), + ], + id="send_eth_from_eoa", + ), + pytest.param( + [ + DepositContract( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ), + ), + ], + id="single_deposit_from_contract", + ), + pytest.param( + [ + DepositContract( + request=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ), + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=0x1, + ), + ], + ), + ], + id="multiple_deposits_from_contract", + ), + pytest.param( + [ + DepositContract( + request=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=i, + ) + for i in range(1000) + ], + tx_gas_limit=60_000_000, + ), + ], + id="many_deposits_from_contract", + ), + pytest.param( + [ + DepositContract( + request=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=999_999_999, + signature=0x03, + index=0x0, + valid=False, + ), + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=0x0, + ), + ], + ), + ], + id="multiple_deposits_from_contract_first_reverts", + ), + pytest.param( + [ + DepositContract( + request=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=0x0, + ), + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=999_999_999, + signature=0x03, + index=0x1, + valid=False, + ), + ], + ), + ], + id="multiple_deposits_from_contract_last_reverts", + ), + pytest.param( + [ + DepositContract( + request=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + gas_limit=100, + index=0x0, + valid=False, + ), + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + gas_limit=1_000_000, + index=0x0, + ), + ], + ), + ], + id="multiple_deposits_from_contract_first_oog", + ), + pytest.param( + [ + DepositContract( + request=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=0x0, + gas_limit=1_000_000, + ), + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=0x0, + gas_limit=100, + valid=False, + ), + ], + ), + ], + id="multiple_deposits_from_contract_last_oog", + ), + pytest.param( + [ + DepositContract( + request=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + valid=False, + ), + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=0x1, + valid=False, + ), + ], + extra_code=Op.REVERT(0, 0), + ), + ], + id="multiple_deposits_from_contract_caller_reverts", + ), + pytest.param( + [ + DepositContract( + request=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + valid=False, + ), + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=0x1, + valid=False, + ), + ], + extra_code=Macros.OOG(), + ), + ], + id="multiple_deposits_from_contract_caller_oog", + ), + pytest.param( + [ + DepositContract( + request=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=i, + valid=False, + ) + for i in range(1000) + ], + tx_gas_limit=23_738_700, + ), + ], + id="many_deposits_from_contract_oog", + ), + pytest.param( + [ + DepositContract( + request=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ), + ], + ), + DepositTransaction( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x1, + ), + ), + ], + id="single_deposit_from_contract_single_deposit_from_eoa", + ), + pytest.param( + [ + DepositTransaction( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ), + ), + DepositContract( + request=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x1, + ), + ], + ), + ], + id="single_deposit_from_eoa_single_deposit_from_contract", + ), + pytest.param( + [ + DepositTransaction( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ), + ), + DepositContract( + request=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x1, + ), + ], + ), + DepositTransaction( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x2, + ), + ), + ], + id="single_deposit_from_contract_between_eoa_deposits", + ), + pytest.param( + [ + DepositContract( + request=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ), + ], + ), + DepositTransaction( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x1, + ), + ), + DepositContract( + request=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x2, + ), + ], + ), + ], + id="single_deposit_from_eoa_between_contract_deposits", + ), + pytest.param( + [ + DepositContract( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + valid=False, + ), + call_type=Op.DELEGATECALL, + ), + ], + id="single_deposit_from_contract_delegatecall", + ), + pytest.param( + [ + DepositContract( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + valid=False, + ), + call_type=Op.STATICCALL, + ), + ], + id="single_deposit_from_contract_staticcall", + ), + pytest.param( + [ + DepositContract( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + valid=False, + ), + call_type=Op.CALLCODE, + ), + ], + id="single_deposit_from_contract_callcode", + ), + pytest.param( + [ + DepositContract( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ), + call_depth=3, + ), + ], + id="single_deposit_from_contract_call_depth_3", + ), + pytest.param( + [ + DepositContract( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ), + call_depth=1024, + tx_gas_limit=2_500_000_000_000, + ), + ], + id="single_deposit_from_contract_call_high_depth", + ), + # TODO: Send eth with the transaction to the contract + ], +) +def test_deposit( + blockchain_test: BlockchainTestFiller, + pre: Dict[Address, Account], + blocks: List[Block], +): + """ + Test making a deposit to the beacon chain deposit contract. + """ + blockchain_test( + genesis_environment=Environment(), + pre=pre, + post={}, + blocks=blocks, + ) + + +@pytest.mark.parametrize( + "requests,block_body_override_requests,exception", + [ + pytest.param( + [], + [ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=0x0, + ), + ], + BlockException.INVALID_REQUESTS, + id="no_deposits_non_empty_requests_list", + ), + pytest.param( + [ + DepositTransaction( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=0x0, + ), + ), + ], + [], + BlockException.INVALID_REQUESTS, + id="single_deposit_empty_requests_list", + ), + pytest.param( + [ + DepositTransaction( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=0x0, + ), + ), + ], + [ + DepositRequest( + pubkey=0x02, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=0x0, + ) + ], + BlockException.INVALID_REQUESTS, + id="single_deposit_pubkey_mismatch", + ), + pytest.param( + [ + DepositTransaction( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=0x0, + ), + ), + ], + [ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x03, + amount=1_000_000_000, + signature=0x03, + index=0x0, + ) + ], + BlockException.INVALID_REQUESTS, + id="single_deposit_credentials_mismatch", + ), + pytest.param( + [ + DepositTransaction( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=0x0, + ), + ), + ], + [ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=2_000_000_000, + signature=0x03, + index=0x0, + ) + ], + BlockException.INVALID_REQUESTS, + id="single_deposit_amount_mismatch", + ), + pytest.param( + [ + DepositTransaction( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=0x0, + ), + ), + ], + [ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x04, + index=0x0, + ) + ], + BlockException.INVALID_REQUESTS, + id="single_deposit_signature_mismatch", + ), + pytest.param( + [ + DepositTransaction( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=0x0, + ), + ), + ], + [ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=0x1, + ) + ], + BlockException.INVALID_REQUESTS, + id="single_deposit_index_mismatch", + ), + pytest.param( + [ + DepositTransaction( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=0x0, + ), + ), + DepositTransaction( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=0x1, + ), + ), + ], + [ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=0x1, + ), + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=0x0, + ), + ], + BlockException.INVALID_REQUESTS, + id="two_deposits_out_of_order", + ), + pytest.param( + [ + DepositTransaction( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=0x0, + ), + ), + ], + [ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=0x0, + ), + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=0x0, + ), + ], + BlockException.INVALID_REQUESTS, + id="single_deposit_duplicate_in_requests_list", + ), + ], +) +def test_deposit_negative( + blockchain_test: BlockchainTestFiller, + pre: Dict[Address, Account], + blocks: List[Block], +): + """ + Test producing a block with the incorrect deposits in the body of the block, + and/or Engine API payload. + """ + blockchain_test( + genesis_environment=Environment(), + pre=pre, + post={}, + blocks=blocks, + ) diff --git a/tests/prague/eip7002_el_triggerable_withdrawals/__init__.py b/tests/prague/eip7002_el_triggerable_withdrawals/__init__.py new file mode 100644 index 0000000000..899bbcbf57 --- /dev/null +++ b/tests/prague/eip7002_el_triggerable_withdrawals/__init__.py @@ -0,0 +1,3 @@ +""" +Cross-client EIP-7002 Tests +""" diff --git a/tests/prague/eip7002_el_triggerable_withdrawals/conftest.py b/tests/prague/eip7002_el_triggerable_withdrawals/conftest.py new file mode 100644 index 0000000000..39deca64e7 --- /dev/null +++ b/tests/prague/eip7002_el_triggerable_withdrawals/conftest.py @@ -0,0 +1,89 @@ +""" +Fixtures for the EIP-7002 deposit tests. +""" +from typing import Dict, List + +import pytest + +from ethereum_test_tools import Account, Address, Block, Header + +from .helpers import WithdrawalRequest, WithdrawalRequestInteractionBase +from .spec import Spec + + +@pytest.fixture +def included_requests( + blocks_withdrawal_requests: List[List[WithdrawalRequestInteractionBase]], +) -> List[List[WithdrawalRequest]]: + """ + Return the list of withdrawal requests that should be included in each block. + """ + excess_withdrawal_requests = 0 + carry_over_requests: List[WithdrawalRequest] = [] + per_block_included_requests: List[List[WithdrawalRequest]] = [] + for block_withdrawal_requests in blocks_withdrawal_requests: + # Get fee for the current block + current_minimum_fee = Spec.get_fee(excess_withdrawal_requests) + + # With the fee, get the valid withdrawal requests for the current block + current_block_requests = [] + for w in block_withdrawal_requests: + current_block_requests += w.valid_requests(current_minimum_fee) + + # Get the withdrawal requests that should be included in the block + pending_requests = carry_over_requests + current_block_requests + per_block_included_requests.append( + pending_requests[: Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK] + ) + carry_over_requests = pending_requests[Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK :] + + # Update the excess withdrawal requests + excess_withdrawal_requests = Spec.get_excess_withdrawal_requests( + excess_withdrawal_requests, + len(current_block_requests), + ) + return per_block_included_requests + + +@pytest.fixture +def pre( + blocks_withdrawal_requests: List[List[WithdrawalRequestInteractionBase]], +) -> Dict[Address, Account]: + """ + Initial state of the accounts. Every withdrawal transaction defines their own pre-state + requirements, and this fixture aggregates them all. + """ + pre: Dict[Address, Account] = {} + for requests in blocks_withdrawal_requests: + for d in requests: + d.update_pre(pre) + return pre + + +@pytest.fixture +def blocks( + blocks_withdrawal_requests: List[List[WithdrawalRequestInteractionBase]], + included_requests: List[List[WithdrawalRequest]], +) -> List[Block]: + """ + Return the list of blocks that should be included in the test. + """ + blocks: List[Block] = [] + address_nonce: Dict[Address, int] = {} + for i in range(len(blocks_withdrawal_requests)): + txs = [] + for r in blocks_withdrawal_requests[i]: + nonce = 0 + if r.sender_account.address in address_nonce: + nonce = address_nonce[r.sender_account.address] + txs.append(r.transaction(nonce)) + address_nonce[r.sender_account.address] = nonce + 1 + blocks.append( + Block( + txs=txs, + header_verify=Header( + requests_root=included_requests[i], + ), + ) + ) + return blocks diff --git a/tests/prague/eip7002_el_triggerable_withdrawals/helpers.py b/tests/prague/eip7002_el_triggerable_withdrawals/helpers.py new file mode 100644 index 0000000000..483e3f9de8 --- /dev/null +++ b/tests/prague/eip7002_el_triggerable_withdrawals/helpers.py @@ -0,0 +1,340 @@ +""" +Helpers for the EIP-7002 deposit tests. +""" +from dataclasses import dataclass, field +from functools import cached_property +from itertools import count +from typing import Callable, ClassVar, Dict, List + +from ethereum_test_tools import Account, Address +from ethereum_test_tools import Opcodes as Op +from ethereum_test_tools import ( + TestAddress, + TestAddress2, + TestPrivateKey, + TestPrivateKey2, + Transaction, +) +from ethereum_test_tools import WithdrawalRequest as WithdrawalRequestBase + +from .spec import Spec + + +@dataclass +class SenderAccount: + """Test sender account descriptor.""" + + address: Address + key: str + + +TestAccount1 = SenderAccount(TestAddress, TestPrivateKey) +TestAccount2 = SenderAccount(TestAddress2, TestPrivateKey2) + + +class WithdrawalRequest(WithdrawalRequestBase): + """ + Class used to describe a withdrawal request in a test. + """ + + fee: int = 0 + """ + Fee to be paid for the withdrawal request. + """ + valid: bool = True + """ + Whether the withdrawal request is valid or not. + """ + gas_limit: int = 1_000_000 + """ + Gas limit for the call. + """ + calldata_modifier: Callable[[bytes], bytes] = lambda x: x + """ + Calldata modifier function. + """ + + interaction_contract_address: ClassVar[Address] = Address( + Spec.WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS + ) + + @property + def value(self) -> int: + """ + Returns the value of the withdrawal request. + """ + return self.fee + + @cached_property + def calldata(self) -> bytes: + """ + Returns the calldata needed to call the withdrawal request contract and make the + withdrawal. + """ + return self.calldata_modifier( + self.validator_public_key + self.amount.to_bytes(8, byteorder="big") + ) + + def with_source_address(self, source_address: Address) -> "WithdrawalRequest": + """ + Return a new instance of the withdrawal request with the source address set. + """ + return self.copy(source_address=source_address) + + +@dataclass(kw_only=True) +class WithdrawalRequestInteractionBase: + """ + Base class for all types of withdrawal transactions we want to test. + """ + + sender_balance: int = 32_000_000_000_000_000_000 * 100 + """ + Balance of the account that sends the transaction. + """ + sender_account: SenderAccount = field( + default_factory=lambda: SenderAccount(TestAddress, TestPrivateKey) + ) + """ + Account that will send the transaction. + """ + + def transaction(self, nonce: int) -> Transaction: + """Return a transaction for the withdrawal request.""" + raise NotImplementedError + + def update_pre(self, base_pre: Dict[Address, Account]): + """Return the pre-state of the account.""" + raise NotImplementedError + + def valid_requests(self, current_minimum_fee: int) -> List[WithdrawalRequest]: + """Return the list of withdrawal requests that should be valid in the block.""" + raise NotImplementedError + + +@dataclass(kw_only=True) +class WithdrawalRequestTransaction(WithdrawalRequestInteractionBase): + """Class used to describe a withdrawal request originated from an externally owned account.""" + + request: WithdrawalRequest + """ + Withdrawal request to be requested by the transaction. + """ + + def transaction(self, nonce: int) -> Transaction: + """Return a transaction for the withdrawal request.""" + return Transaction( + nonce=nonce, + gas_limit=self.request.gas_limit, + gas_price=0x07, + to=self.request.interaction_contract_address, + value=self.request.value, + data=self.request.calldata, + secret_key=self.sender_account.key, + ) + + def update_pre(self, base_pre: Dict[Address, Account]): + """Return the pre-state of the account.""" + base_pre.update( + { + self.sender_account.address: Account(balance=self.sender_balance), + } + ) + + def valid_requests(self, current_minimum_fee: int) -> List[WithdrawalRequest]: + """Return the list of withdrawal requests that are valid.""" + if self.request.valid and self.request.fee >= current_minimum_fee: + return [self.request.with_source_address(self.sender_account.address)] + return [] + + +@dataclass(kw_only=True) +class WithdrawalRequestContract(WithdrawalRequestInteractionBase): + """Class used to describe a deposit originated from a contract.""" + + request: List[WithdrawalRequest] | WithdrawalRequest + """ + Withdrawal request or list of withdrawal requests to be requested by the contract. + """ + + tx_gas_limit: int = 1_000_000 + """ + Gas limit for the transaction. + """ + + contract_balance: int = 32_000_000_000_000_000_000 * 100 + """ + Balance of the contract that will make the call to the pre-deploy contract. + """ + contract_address: int = 0x200 + """ + Address of the contract that will make the call to the pre-deploy contract. + """ + + call_type: Op = field(default_factory=lambda: Op.CALL) + """ + Type of call to be used to make the withdrawal request. + """ + call_depth: int = 2 + """ + Frame depth of the pre-deploy contract when it executes the call. + """ + extra_code: bytes = b"" + """ + Extra code to be added to the contract code. + """ + + @property + def requests(self) -> List[WithdrawalRequest]: + """Return the list of withdrawal requests.""" + if not isinstance(self.request, List): + return [self.request] + return self.request + + @property + def contract_code(self) -> bytes: + """Contract code used by the relay contract.""" + code = b"" + current_offset = 0 + for r in self.requests: + value_arg = [r.value] if self.call_type in (Op.CALL, Op.CALLCODE) else [] + code += Op.CALLDATACOPY(0, current_offset, len(r.calldata)) + Op.POP( + self.call_type( + Op.GAS if r.gas_limit == -1 else r.gas_limit, + r.interaction_contract_address, + *value_arg, + 0, + len(r.calldata), + 0, + 0, + ) + ) + current_offset += len(r.calldata) + return code + self.extra_code + + def transaction(self, nonce: int) -> Transaction: + """Return a transaction for the deposit request.""" + return Transaction( + nonce=nonce, + gas_limit=self.tx_gas_limit, + gas_price=0x07, + to=self.entry_address(), + value=0, + data=b"".join(r.calldata for r in self.requests), + secret_key=self.sender_account.key, + ) + + def entry_address(self) -> Address: + """Return the address of the contract entry point.""" + if self.call_depth == 2: + return Address(self.contract_address) + elif self.call_depth > 2: + return Address(self.contract_address + self.call_depth - 2) + raise ValueError("Invalid call depth") + + def extra_contracts(self) -> Dict[Address, Account]: + """Extra contracts used to simulate call depth.""" + if self.call_depth <= 2: + return {} + return { + Address(self.contract_address + i): Account( + balance=self.contract_balance, + code=Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + + Op.POP( + Op.CALL( + Op.GAS, + self.contract_address + i - 1, + 0, + 0, + Op.CALLDATASIZE, + 0, + 0, + ) + ), + nonce=1, + ) + for i in range(1, self.call_depth - 1) + } + + def update_pre(self, base_pre: Dict[Address, Account]): + """Return the pre-state of the account.""" + while Address(self.contract_address) in base_pre: + self.contract_address += 0x100 + base_pre.update( + { + self.sender_account.address: Account(balance=self.sender_balance), + Address(self.contract_address): Account( + balance=self.contract_balance, code=self.contract_code, nonce=1 + ), + } + ) + base_pre.update(self.extra_contracts()) + + def valid_requests(self, current_minimum_fee: int) -> List[WithdrawalRequest]: + """Return the list of withdrawal requests that are valid.""" + valid_requests: List[WithdrawalRequest] = [] + for r in self.requests: + if r.valid and r.value >= current_minimum_fee: + valid_requests.append(r.with_source_address(Address(self.contract_address))) + return valid_requests + + +def get_n_fee_increments(n: int) -> List[int]: + """ + Get the first N excess withdrawal requests that increase the fee. + """ + excess_withdrawal_requests_counts = [] + last_fee = 1 + for i in count(0): + if Spec.get_fee(i) > last_fee: + excess_withdrawal_requests_counts.append(i) + last_fee = Spec.get_fee(i) + if len(excess_withdrawal_requests_counts) == n: + break + return excess_withdrawal_requests_counts + + +def get_n_fee_increment_blocks(n: int) -> List[List[WithdrawalRequestContract]]: + """ + Return N blocks that should be included in the test such that each subsequent block has an + increasing fee for the withdrawal requests. + + This is done by calculating the number of withdrawals required to reach the next fee increment + and creating a block with that number of withdrawal requests plus the number of withdrawals + required to reach the target. + """ + blocks = [] + previous_excess = 0 + nonce = count(0) + withdrawal_index = 0 + previous_fee = 0 + for required_excess_withdrawals in get_n_fee_increments(n): + withdrawals_required = ( + required_excess_withdrawals + + Spec.TARGET_WITHDRAWAL_REQUESTS_PER_BLOCK + - previous_excess + ) + contract_address = next(nonce) + fee = Spec.get_fee(previous_excess) + assert fee > previous_fee + blocks.append( + [ + WithdrawalRequestContract( + request=[ + WithdrawalRequest( + validator_public_key=i, + amount=0, + fee=fee, + ) + for i in range(withdrawal_index, withdrawal_index + withdrawals_required) + ], + # Increment the contract address to avoid overwriting the previous one + contract_address=0x200 + (contract_address * 0x100), + ) + ], + ) + previous_fee = fee + withdrawal_index += withdrawals_required + previous_excess = required_excess_withdrawals + + return blocks diff --git a/tests/prague/eip7002_el_triggerable_withdrawals/spec.py b/tests/prague/eip7002_el_triggerable_withdrawals/spec.py new file mode 100644 index 0000000000..f56381e437 --- /dev/null +++ b/tests/prague/eip7002_el_triggerable_withdrawals/spec.py @@ -0,0 +1,89 @@ +""" +Common procedures to test +[EIP-7002: Execution layer triggerable withdrawals](https://eips.ethereum.org/EIPS/eip-7002) +""" # noqa: E501 + +from dataclasses import dataclass + + +@dataclass(frozen=True) +class ReferenceSpec: + """ + Defines the reference spec version and git path. + """ + + git_path: str + version: str + + +ref_spec_7002 = ReferenceSpec("EIPS/eip-7002.md", "e5af719767e789c88c0e063406c6557c8f53cfba") + + +# Constants +@dataclass(frozen=True) +class Spec: + """ + Parameters from the EIP-7002 specifications as defined at + https://eips.ethereum.org/EIPS/eip-7002#configuration + + If the parameter is not currently used within the tests, it is commented + out. + """ + + WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS = 0x00A3CA265EBCB825B45F985A16CEFB49958CE017 + SYSTEM_ADDRESS = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE + + EXCESS_WITHDRAWAL_REQUESTS_STORAGE_SLOT = 0 + WITHDRAWAL_REQUEST_COUNT_STORAGE_SLOT = 1 + WITHDRAWAL_REQUEST_QUEUE_HEAD_STORAGE_SLOT = ( + 2 # Pointer to head of the withdrawal request message queue + ) + WITHDRAWAL_REQUEST_QUEUE_TAIL_STORAGE_SLOT = ( + 3 # Pointer to the tail of the withdrawal request message queue + ) + WITHDRAWAL_REQUEST_QUEUE_STORAGE_OFFSET = ( + 4 # The start memory slot of the in-state withdrawal request message queue + ) + MAX_WITHDRAWAL_REQUESTS_PER_BLOCK = ( + 16 # Maximum number of withdrawal requests that can be de-queued into a block + ) + TARGET_WITHDRAWAL_REQUESTS_PER_BLOCK = 2 + MIN_WITHDRAWAL_REQUEST_FEE = 1 + WITHDRAWAL_REQUEST_FEE_UPDATE_FRACTION = 17 + EXCESS_RETURN_GAS_STIPEND = 2300 + + MAX_AMOUNT = 2**64 - 1 + + @staticmethod + def fake_exponential(factor: int, numerator: int, denominator: int) -> int: + """ + Used to calculate the withdrawal request fee. + """ + i = 1 + output = 0 + numerator_accumulator = factor * denominator + while numerator_accumulator > 0: + output += numerator_accumulator + numerator_accumulator = (numerator_accumulator * numerator) // (denominator * i) + i += 1 + return output // denominator + + @staticmethod + def get_fee(excess_withdrawal_requests: int) -> int: + """ + Calculate the fee for the excess withdrawal requests. + """ + return Spec.fake_exponential( + Spec.MIN_WITHDRAWAL_REQUEST_FEE, + excess_withdrawal_requests, + Spec.WITHDRAWAL_REQUEST_FEE_UPDATE_FRACTION, + ) + + @staticmethod + def get_excess_withdrawal_requests(previous_excess: int, count: int) -> int: + """ + Calculate the new excess withdrawal requests. + """ + if previous_excess + count > Spec.TARGET_WITHDRAWAL_REQUESTS_PER_BLOCK: + return previous_excess + count - Spec.TARGET_WITHDRAWAL_REQUESTS_PER_BLOCK + return 0 diff --git a/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests.py b/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests.py new file mode 100644 index 0000000000..bd9bbd3bf2 --- /dev/null +++ b/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests.py @@ -0,0 +1,694 @@ +""" +abstract: Tests [EIP-7002: Execution layer triggerable withdrawals](https://eips.ethereum.org/EIPS/eip-7002) + Test execution layer triggered exits [EIP-7002: Execution layer triggerable withdrawals](https://eips.ethereum.org/EIPS/eip-7002) + +""" # noqa: E501 + +from typing import Dict, List + +import pytest + +from ethereum_test_tools import ( + Account, + Address, + Block, + BlockchainTestFiller, + BlockException, + Environment, + Header, + Macros, +) +from ethereum_test_tools import Opcodes as Op +from ethereum_test_tools import TestAddress, TestAddress2 + +from .helpers import ( + TestAccount2, + WithdrawalRequest, + WithdrawalRequestContract, + WithdrawalRequestInteractionBase, + WithdrawalRequestTransaction, + get_n_fee_increment_blocks, +) +from .spec import Spec, ref_spec_7002 + +REFERENCE_SPEC_GIT_PATH = ref_spec_7002.git_path +REFERENCE_SPEC_VERSION = ref_spec_7002.version + +pytestmark = pytest.mark.valid_from("Prague") + + +@pytest.mark.parametrize( + "blocks_withdrawal_requests", + [ + pytest.param( + [ + [ + WithdrawalRequestTransaction( + request=WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=Spec.get_fee(0), + ), + ), + ], + ], + id="single_block_single_withdrawal_request_from_eoa", + ), + pytest.param( + [ + [ + WithdrawalRequestTransaction( + request=WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=0, + ), + ), + ], + ], + id="single_block_single_withdrawal_request_from_eoa_insufficient_fee", + ), + pytest.param( + [ + [ + WithdrawalRequestTransaction( + request=WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=Spec.get_fee(0), + calldata_modifier=lambda x: x[:-1], + valid=False, + ), + ), + ], + ], + id="single_block_single_withdrawal_request_from_eoa_input_too_short", + ), + pytest.param( + [ + [ + WithdrawalRequestTransaction( + request=WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=Spec.get_fee(0), + calldata_modifier=lambda x: x + b"\x00", + valid=False, + ), + ), + ], + ], + id="single_block_single_withdrawal_request_from_eoa_input_too_long", + ), + pytest.param( + [ + [ + WithdrawalRequestTransaction( + request=WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=Spec.get_fee(0), + ), + ), + WithdrawalRequestTransaction( + request=WithdrawalRequest( + validator_public_key=0x02, + amount=Spec.MAX_AMOUNT - 1, + fee=Spec.get_fee(0), + ), + ), + ], + ], + id="single_block_multiple_withdrawal_request_from_same_eoa", + ), + pytest.param( + [ + [ + WithdrawalRequestTransaction( + request=WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=Spec.get_fee(0), + ), + ), + WithdrawalRequestTransaction( + request=WithdrawalRequest( + validator_public_key=0x02, + amount=Spec.MAX_AMOUNT - 1, + fee=Spec.get_fee(0), + ), + sender_account=TestAccount2, + ), + ], + ], + id="single_block_multiple_withdrawal_request_from_different_eoa", + ), + pytest.param( + [ + [ + WithdrawalRequestTransaction( + request=WithdrawalRequest( + validator_public_key=i + 1, + amount=0 if i % 2 == 0 else Spec.MAX_AMOUNT, + fee=Spec.get_fee(0), + ), + ) + for i in range(Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK) + ], + ], + id="single_block_max_withdrawal_requests_from_eoa", + ), + pytest.param( + [ + [ + WithdrawalRequestTransaction( + request=WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=0, + ), + ), + WithdrawalRequestTransaction( + request=WithdrawalRequest( + validator_public_key=0x02, + amount=Spec.MAX_AMOUNT - 1, + fee=Spec.get_fee(0), + ), + ), + ], + ], + id="single_block_multiple_withdrawal_request_first_reverts", + ), + pytest.param( + [ + [ + WithdrawalRequestTransaction( + request=WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=Spec.get_fee(0), + ), + ), + WithdrawalRequestTransaction( + request=WithdrawalRequest( + validator_public_key=0x02, + amount=Spec.MAX_AMOUNT - 1, + fee=0, + ), + ), + ], + ], + id="single_block_multiple_withdrawal_request_last_reverts", + ), + pytest.param( + [ + [ + WithdrawalRequestTransaction( + request=WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=Spec.get_fee(0), + # Value obtained from trace minus one + gas_limit=114_247 - 1, + valid=False, + ), + ), + WithdrawalRequestTransaction( + request=WithdrawalRequest( + validator_public_key=0x02, + amount=0, + fee=Spec.get_fee(0), + ), + ), + ], + ], + id="single_block_multiple_withdrawal_request_first_oog", + ), + pytest.param( + [ + [ + WithdrawalRequestTransaction( + request=WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=Spec.get_fee(0), + ), + ), + WithdrawalRequestTransaction( + request=WithdrawalRequest( + validator_public_key=0x02, + amount=0, + fee=Spec.get_fee(0), + # Value obtained from trace minus one + gas_limit=80_047 - 1, + valid=False, + ), + ), + ], + ], + id="single_block_multiple_withdrawal_request_last_oog", + ), + pytest.param( + [ + # Block 1 + [ + WithdrawalRequestTransaction( + request=WithdrawalRequest( + validator_public_key=i + 1, + amount=0 if i % 2 == 0 else Spec.MAX_AMOUNT, + fee=Spec.get_fee(0), + ), + ) + for i in range(Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK * 2) + ], + # Block 2, no new withdrawal requests, but queued requests from previous block + [], + # Block 3, no new nor queued withdrawal requests + [], + ], + id="multiple_block_above_max_withdrawal_requests_from_eoa", + ), + pytest.param( + [ + [ + WithdrawalRequestContract( + request=WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=Spec.get_fee(0), + ), + ), + ], + ], + id="single_block_single_withdrawal_request_from_contract", + ), + pytest.param( + [ + [ + WithdrawalRequestContract( + request=[ + WithdrawalRequest( + validator_public_key=i + 1, + amount=Spec.MAX_AMOUNT - 1 if i % 2 == 0 else 0, + fee=Spec.get_fee(0), + ) + for i in range(Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK) + ], + ), + ], + ], + id="single_block_multiple_withdrawal_requests_from_contract", + ), + pytest.param( + [ + [ + WithdrawalRequestContract( + request=[ + WithdrawalRequest( + validator_public_key=1, + amount=Spec.MAX_AMOUNT, + fee=0, + ) + ] + + [ + WithdrawalRequest( + validator_public_key=i + 1, + amount=Spec.MAX_AMOUNT - 1 if i % 2 == 0 else 0, + fee=Spec.get_fee(0), + ) + for i in range(1, Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK) + ], + ), + ], + ], + id="single_block_multiple_withdrawal_requests_from_contract_first_reverts", + ), + pytest.param( + [ + [ + WithdrawalRequestContract( + request=[ + WithdrawalRequest( + validator_public_key=i + 1, + amount=Spec.MAX_AMOUNT - 1 if i % 2 == 0 else 0, + fee=Spec.get_fee(0), + ) + for i in range(Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK - 1) + ] + + [ + WithdrawalRequest( + validator_public_key=Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK, + amount=Spec.MAX_AMOUNT - 1 + if (Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK - 1) % 2 == 0 + else 0, + fee=0, + ) + ], + ), + ], + ], + id="single_block_multiple_withdrawal_requests_from_contract_last_reverts", + ), + pytest.param( + [ + [ + WithdrawalRequestContract( + request=[ + WithdrawalRequest( + validator_public_key=1, + amount=Spec.MAX_AMOUNT - 1, + gas_limit=100, + fee=Spec.get_fee(0), + valid=False, + ) + ] + + [ + WithdrawalRequest( + validator_public_key=i + 1, + amount=Spec.MAX_AMOUNT - 1 if i % 2 == 0 else 0, + gas_limit=1_000_000, + fee=Spec.get_fee(0), + valid=True, + ) + for i in range(1, Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK) + ], + ), + ], + ], + id="single_block_multiple_withdrawal_requests_from_contract_first_oog", + ), + pytest.param( + [ + [ + WithdrawalRequestContract( + request=[ + WithdrawalRequest( + validator_public_key=i + 1, + amount=Spec.MAX_AMOUNT - 1 if i % 2 == 0 else 0, + fee=Spec.get_fee(0), + gas_limit=1_000_000, + valid=True, + ) + for i in range(Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK) + ] + + [ + WithdrawalRequest( + validator_public_key=Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK, + amount=Spec.MAX_AMOUNT - 1, + gas_limit=100, + fee=Spec.get_fee(0), + valid=False, + ) + ], + ), + ], + ], + id="single_block_multiple_withdrawal_requests_from_contract_last_oog", + ), + pytest.param( + [ + [ + WithdrawalRequestContract( + request=[ + WithdrawalRequest( + validator_public_key=i + 1, + amount=Spec.MAX_AMOUNT - 1 if i % 2 == 0 else 0, + fee=Spec.get_fee(0), + valid=False, + ) + for i in range(Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK) + ], + extra_code=Op.REVERT(0, 0), + ), + ], + ], + id="single_block_multiple_withdrawal_requests_from_contract_caller_reverts", + ), + pytest.param( + [ + [ + WithdrawalRequestContract( + request=[ + WithdrawalRequest( + validator_public_key=i + 1, + amount=Spec.MAX_AMOUNT - 1 if i % 2 == 0 else 0, + fee=Spec.get_fee(0), + valid=False, + ) + for i in range(Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK) + ], + extra_code=Macros.OOG(), + ), + ], + ], + id="single_block_multiple_withdrawal_requests_from_contract_caller_oog", + ), + pytest.param( + # Test the first 50 fee increments + get_n_fee_increment_blocks(50), + id="multiple_block_fee_increments", + ), + pytest.param( + [ + [ + WithdrawalRequestContract( + request=WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=Spec.get_fee(0), + valid=False, + ), + call_type=Op.DELEGATECALL, + ), + WithdrawalRequestContract( + request=WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=Spec.get_fee(0), + valid=False, + ), + call_type=Op.STATICCALL, + ), + WithdrawalRequestContract( + request=WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=Spec.get_fee(0), + valid=False, + ), + call_type=Op.CALLCODE, + ), + ], + ], + id="single_block_single_withdrawal_request_delegatecall_staticcall_callcode", + ), + ], +) +def test_withdrawal_requests( + blockchain_test: BlockchainTestFiller, + blocks: List[Block], + pre: Dict[Address, Account], +): + """ + Test making a withdrawal request to the beacon chain. + """ + blockchain_test( + genesis_environment=Environment(), + pre=pre, + post={}, + blocks=blocks, + ) + + +@pytest.mark.parametrize( + "requests,block_body_override_requests,exception", + [ + pytest.param( + [], + [ + WithdrawalRequest( + validator_public_key=0x01, + amount=0, + source_address=Address(0), + ), + ], + BlockException.INVALID_REQUESTS, + id="no_withdrawals_non_empty_requests_list", + ), + pytest.param( + [ + WithdrawalRequestTransaction( + request=WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=Spec.get_fee(0), + ), + ), + ], + [], + BlockException.INVALID_REQUESTS, + id="single_withdrawal_request_empty_requests_list", + ), + pytest.param( + [ + WithdrawalRequestTransaction( + request=WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=Spec.get_fee(0), + ), + ), + ], + [ + WithdrawalRequest( + validator_public_key=0x02, + amount=0, + source_address=TestAddress, + ) + ], + BlockException.INVALID_REQUESTS, + id="single_withdrawal_request_public_key_mismatch", + ), + pytest.param( + [ + WithdrawalRequestTransaction( + request=WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=Spec.get_fee(0), + ), + ), + ], + [ + WithdrawalRequest( + validator_public_key=0x01, + amount=1, + source_address=TestAddress, + ) + ], + BlockException.INVALID_REQUESTS, + id="single_withdrawal_request_amount_mismatch", + ), + pytest.param( + [ + WithdrawalRequestTransaction( + request=WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=Spec.get_fee(0), + ), + ), + ], + [ + WithdrawalRequest( + validator_public_key=0x01, + amount=0, + source_address=TestAddress2, + ) + ], + BlockException.INVALID_REQUESTS, + id="single_withdrawal_request_source_address_mismatch", + ), + pytest.param( + [ + WithdrawalRequestTransaction( + request=WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=Spec.get_fee(0), + ), + ), + WithdrawalRequestTransaction( + request=WithdrawalRequest( + validator_public_key=0x02, + amount=0, + fee=Spec.get_fee(0), + ), + ), + ], + [ + WithdrawalRequest( + validator_public_key=0x02, + amount=0, + source_address=TestAddress, + ), + WithdrawalRequest( + validator_public_key=0x01, + amount=0, + source_address=TestAddress, + ), + ], + BlockException.INVALID_REQUESTS, + id="two_withdrawal_requests_out_of_order", + ), + pytest.param( + [ + WithdrawalRequestTransaction( + request=WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=Spec.get_fee(0), + ), + ), + ], + [ + WithdrawalRequest( + validator_public_key=0x01, + amount=0, + source_address=TestAddress, + ), + WithdrawalRequest( + validator_public_key=0x01, + amount=0, + source_address=TestAddress, + ), + ], + BlockException.INVALID_REQUESTS, + id="single_withdrawal_requests_duplicate_in_requests_list", + ), + ], +) +def test_withdrawal_requests_negative( + blockchain_test: BlockchainTestFiller, + requests: List[WithdrawalRequestInteractionBase], + block_body_override_requests: List[WithdrawalRequest], + exception: BlockException, +): + """ + Test blocks where the requests list and the actual withdrawal requests that happened in the + block's transactions do not match. + """ + # No previous block so fee is the base + fee = 1 + current_block_requests = [] + for w in requests: + current_block_requests += w.valid_requests(fee) + included_requests = current_block_requests[: Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK] + + pre: Dict[Address, Account] = {} + for d in requests: + d.update_pre(pre) + + address_nonce: Dict[Address, int] = {} + txs = [] + for r in requests: + nonce = 0 + if r.sender_account.address in address_nonce: + nonce = address_nonce[r.sender_account.address] + txs.append(r.transaction(nonce)) + address_nonce[r.sender_account.address] = nonce + 1 + blockchain_test( + genesis_environment=Environment(), + pre=pre, + post={}, + blocks=[ + Block( + txs=txs, + header_verify=Header( + requests_root=included_requests, + ), + requests=block_body_override_requests, + exception=exception, + ) + ], + ) diff --git a/tests/prague/eip7685_general_purpose_el_requests/__init__.py b/tests/prague/eip7685_general_purpose_el_requests/__init__.py new file mode 100644 index 0000000000..8d782b5a61 --- /dev/null +++ b/tests/prague/eip7685_general_purpose_el_requests/__init__.py @@ -0,0 +1,3 @@ +""" +Cross-client EIP-7685 Tests +""" diff --git a/tests/prague/eip7685_general_purpose_el_requests/conftest.py b/tests/prague/eip7685_general_purpose_el_requests/conftest.py new file mode 100644 index 0000000000..16cb104a7e --- /dev/null +++ b/tests/prague/eip7685_general_purpose_el_requests/conftest.py @@ -0,0 +1,87 @@ +""" +Fixtures for the EIP-7685 deposit tests. +""" + +from typing import Dict, List + +import pytest + +from ethereum_test_tools import Account, Address, Block, BlockException, Header, Transaction + +from ..eip6110_deposits.helpers import DepositInteractionBase, DepositRequest +from ..eip7002_el_triggerable_withdrawals.helpers import ( + WithdrawalRequest, + WithdrawalRequestInteractionBase, +) + + +@pytest.fixture +def pre( + requests: List[DepositInteractionBase | WithdrawalRequestInteractionBase], +) -> Dict[Address, Account]: + """ + Initial state of the accounts. Every deposit transaction defines their own pre-state + requirements, and this fixture aggregates them all. + """ + pre: Dict[Address, Account] = {} + for d in requests: + d.update_pre(pre) + return pre + + +@pytest.fixture +def txs( + requests: List[DepositInteractionBase | WithdrawalRequestInteractionBase], +) -> List[Transaction]: + """List of transactions to include in the block.""" + address_nonce: Dict[Address, int] = {} + txs = [] + for r in requests: + nonce = 0 + if r.sender_account.address in address_nonce: + nonce = address_nonce[r.sender_account.address] + txs.append(r.transaction(nonce)) + address_nonce[r.sender_account.address] = nonce + 1 + return txs + + +@pytest.fixture +def block_body_override_requests() -> List[DepositRequest] | None: + """List of requests that overwrite the requests in the header. None by default.""" + return None + + +@pytest.fixture +def exception() -> BlockException | None: + """Block exception expected by the tests. None by default.""" + return None + + +@pytest.fixture +def blocks( + requests: List[DepositInteractionBase | WithdrawalRequestInteractionBase], + block_body_override_requests: List[DepositRequest | WithdrawalRequest] | None, + txs: List[Transaction], + exception: BlockException | None, +) -> List[Block]: + """List of blocks that comprise the test.""" + included_deposit_requests = [] + included_withdrawal_requests = [] + # Single block therefore base fee + withdrawal_request_fee = 1 + for r in requests: + if isinstance(r, DepositInteractionBase): + included_deposit_requests += r.valid_requests(10**18) + elif isinstance(r, WithdrawalRequestInteractionBase): + included_withdrawal_requests += r.valid_requests(withdrawal_request_fee) + + return [ + Block( + txs=txs, + header_verify=Header( + requests_root=included_deposit_requests + included_withdrawal_requests, + ), + requests=block_body_override_requests, + exception=exception, + ) + ] diff --git a/tests/prague/eip7685_general_purpose_el_requests/spec.py b/tests/prague/eip7685_general_purpose_el_requests/spec.py new file mode 100644 index 0000000000..d4b7d6dc0c --- /dev/null +++ b/tests/prague/eip7685_general_purpose_el_requests/spec.py @@ -0,0 +1,19 @@ +""" +Common procedures to test +[EIP-7685: General purpose execution layer requests](https://eips.ethereum.org/EIPS/eip-7685) +""" # noqa: E501 + +from dataclasses import dataclass + + +@dataclass(frozen=True) +class ReferenceSpec: + """ + Defines the reference spec version and git path. + """ + + git_path: str + version: str + + +ref_spec_7685 = ReferenceSpec("EIPS/eip-7685.md", "52a260582376476e658b1dda60864bcac3cf5e1a") diff --git a/tests/prague/eip7685_general_purpose_el_requests/test_deposits_withdrawals.py b/tests/prague/eip7685_general_purpose_el_requests/test_deposits_withdrawals.py new file mode 100644 index 0000000000..b7356fdce8 --- /dev/null +++ b/tests/prague/eip7685_general_purpose_el_requests/test_deposits_withdrawals.py @@ -0,0 +1,375 @@ +""" +abstract: Tests [EIP-7685: General purpose execution layer requests](https://eips.ethereum.org/EIPS/eip-7685) + Cross testing for withdrawal and deposit request for [EIP-7685: General purpose execution layer requests](https://eips.ethereum.org/EIPS/eip-7685) + +""" # noqa: E501 + +from typing import Dict, List + +import pytest + +from ethereum_test_tools import ( + Account, + Address, + Block, + BlockchainTestFiller, + BlockException, + Environment, + Header, +) +from ethereum_test_tools import Opcodes as Op +from ethereum_test_tools import TestAddress, Transaction + +from ..eip6110_deposits.helpers import DepositContract, DepositRequest, DepositTransaction +from ..eip6110_deposits.spec import Spec as Spec_EIP6110 +from ..eip7002_el_triggerable_withdrawals.helpers import ( + WithdrawalRequest, + WithdrawalRequestContract, + WithdrawalRequestTransaction, +) +from ..eip7002_el_triggerable_withdrawals.spec import Spec as Spec_EIP7002 +from .spec import ref_spec_7685 + +REFERENCE_SPEC_GIT_PATH = ref_spec_7685.git_path +REFERENCE_SPEC_VERSION = ref_spec_7685.version + +pytestmark = pytest.mark.valid_from("Prague") + + +@pytest.mark.parametrize( + "requests", + [ + pytest.param( + [ + DepositTransaction( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ), + ), + WithdrawalRequestTransaction( + request=WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=1, + ), + ), + ], + id="single_deposit_from_eoa_single_withdrawal_from_eoa", + ), + pytest.param( + [ + WithdrawalRequestTransaction( + request=WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=1, + ), + ), + DepositTransaction( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ), + ), + ], + id="single_withdrawal_from_eoa_single_deposit_from_eoa", + ), + pytest.param( + [ + DepositTransaction( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ), + ), + WithdrawalRequestTransaction( + request=WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=1, + ), + ), + DepositTransaction( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x1, + ), + ), + ], + id="two_deposits_from_eoa_single_withdrawal_from_eoa", + ), + pytest.param( + [ + WithdrawalRequestTransaction( + request=WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=1, + ), + ), + DepositTransaction( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ), + ), + WithdrawalRequestTransaction( + request=WithdrawalRequest( + validator_public_key=0x01, + amount=1, + fee=1, + ), + ), + ], + id="two_withdrawals_from_eoa_single_deposit_from_eoa", + ), + pytest.param( + [ + DepositContract( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ), + ), + WithdrawalRequestContract( + request=WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=1, + ), + ), + ], + id="single_deposit_from_contract_single_withdrawal_from_contract", + ), + pytest.param( + [ + WithdrawalRequestContract( + request=WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=1, + ), + ), + DepositContract( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ), + ), + ], + id="single_withdrawal_from_contract_single_deposit_from_contract", + ), + # TODO: Deposit and withdrawal in the same transaction + ], +) +def test_valid_deposit_withdrawal_requests( + blockchain_test: BlockchainTestFiller, + pre: Dict[Address, Account], + blocks: List[Block], +): + """ + Test making a deposit to the beacon chain deposit contract and a withdrawal in the same block. + """ + blockchain_test( + genesis_environment=Environment(), + pre=pre, + post={}, + blocks=blocks, + ) + + +@pytest.mark.parametrize( + "deposit_first", + [ + pytest.param(True, id="deposit_first"), + pytest.param(False, id="withdrawal_first"), + ], +) +def test_valid_deposit_withdrawal_request_from_same_tx( + blockchain_test: BlockchainTestFiller, + deposit_first: bool, +): + """ + Test making a deposit to the beacon chain deposit contract and a withdrawal in the same tx. + """ + contract_address = 0x200 + withdrawal_request_fee = 1 + deposit_request = DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ) + withdrawal_request = WithdrawalRequest( + validator_public_key=0x01, + amount=0, + source_address=contract_address, + ) + if deposit_first: + calldata = deposit_request.calldata + withdrawal_request.calldata + contract_code = ( + Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + + Op.POP( + Op.CALL( + Op.GAS, + Spec_EIP6110.DEPOSIT_CONTRACT_ADDRESS, + deposit_request.value, + 0, + len(deposit_request.calldata), + 0, + 0, + ) + ) + + Op.POP( + Op.CALL( + Op.GAS, + Spec_EIP7002.WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, + withdrawal_request_fee, + len(deposit_request.calldata), + len(withdrawal_request.calldata), + 0, + 0, + ) + ) + ) + else: + calldata = withdrawal_request.calldata + deposit_request.calldata + contract_code = ( + Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + + Op.POP( + Op.CALL( + Op.GAS, + Spec_EIP7002.WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, + withdrawal_request_fee, + 0, + len(withdrawal_request.calldata), + 0, + 0, + ) + ) + + Op.POP( + Op.CALL( + Op.GAS, + Spec_EIP6110.DEPOSIT_CONTRACT_ADDRESS, + deposit_request.value, + len(withdrawal_request.calldata), + len(deposit_request.calldata), + 0, + 0, + ) + ) + ) + + pre = { + TestAddress: Account( + balance=10**18, + ), + contract_address: Account( + code=contract_code, + balance=deposit_request.value + withdrawal_request_fee, + ), + } + + tx = Transaction( + nonce=0, + gas_limit=1_000_000, + gas_price=0x07, + to=contract_address, + value=0, + data=calldata, + ) + + block = Block( + txs=[tx], + header_verify=Header( + requests_root=[deposit_request, withdrawal_request], + ), + ) + + blockchain_test( + genesis_environment=Environment(), + pre=pre, + post={}, + blocks=[block], + ) + + +@pytest.mark.parametrize( + "requests,block_body_override_requests,exception", + [ + pytest.param( + [ + WithdrawalRequestTransaction( + request=WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=1, + ), + ), + DepositTransaction( + request=DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ), + ), + ], + [ + WithdrawalRequest( + validator_public_key=0x01, + amount=0, + source_address=TestAddress, + ), + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ), + ], + # TODO: on the Engine API, the issue should be detected as an invalid block hash + BlockException.INVALID_REQUESTS, + id="single_deposit_from_eoa_single_withdrawal_from_eoa_incorrect_order", + ), + ], +) +def test_invalid_deposit_withdrawal_requests( + blockchain_test: BlockchainTestFiller, + pre: Dict[Address, Account], + blocks: List[Block], +): + """ + Negative testing for deposits and withdrawals in the same block. + """ + blockchain_test( + genesis_environment=Environment(), + pre=pre, + post={}, + blocks=blocks, + ) diff --git a/tests/prague/eip7692_eof_v1/__init__.py b/tests/prague/eip7692_eof_v1/__init__.py new file mode 100644 index 0000000000..76ffee7645 --- /dev/null +++ b/tests/prague/eip7692_eof_v1/__init__.py @@ -0,0 +1,5 @@ +""" +Test cases for all EIPs mentioned in the EOF V1 meta-EIP. +""" + +EOF_FORK_NAME = "CancunEIP7692" diff --git a/tests/prague/eip3540_eof_v1/__init__.py b/tests/prague/eip7692_eof_v1/eip3540_eof_v1/__init__.py similarity index 100% rename from tests/prague/eip3540_eof_v1/__init__.py rename to tests/prague/eip7692_eof_v1/eip3540_eof_v1/__init__.py diff --git a/tests/prague/eip3540_eof_v1/code_validation.py b/tests/prague/eip7692_eof_v1/eip3540_eof_v1/code_validation.py similarity index 100% rename from tests/prague/eip3540_eof_v1/code_validation.py rename to tests/prague/eip7692_eof_v1/eip3540_eof_v1/code_validation.py diff --git a/tests/prague/eip3540_eof_v1/code_validation_function.py b/tests/prague/eip7692_eof_v1/eip3540_eof_v1/code_validation_function.py similarity index 100% rename from tests/prague/eip3540_eof_v1/code_validation_function.py rename to tests/prague/eip7692_eof_v1/eip3540_eof_v1/code_validation_function.py diff --git a/tests/prague/eip3540_eof_v1/code_validation_jump.py b/tests/prague/eip7692_eof_v1/eip3540_eof_v1/code_validation_jump.py similarity index 100% rename from tests/prague/eip3540_eof_v1/code_validation_jump.py rename to tests/prague/eip7692_eof_v1/eip3540_eof_v1/code_validation_jump.py diff --git a/tests/prague/eip3540_eof_v1/container.py b/tests/prague/eip7692_eof_v1/eip3540_eof_v1/container.py similarity index 69% rename from tests/prague/eip3540_eof_v1/container.py rename to tests/prague/eip7692_eof_v1/eip3540_eof_v1/container.py index 39aefd64a8..f6a51e783a 100644 --- a/tests/prague/eip3540_eof_v1/container.py +++ b/tests/prague/eip7692_eof_v1/eip3540_eof_v1/container.py @@ -10,7 +10,9 @@ from ethereum_test_tools.eof.v1.constants import ( MAX_CODE_INPUTS, MAX_CODE_OUTPUTS, + MAX_CODE_SECTIONS, MAX_OPERAND_STACK_HEIGHT, + NON_RETURNING_SECTION, ) from ethereum_test_tools.exceptions import EOFException from ethereum_test_tools.vm.opcode import Opcodes as Op @@ -19,34 +21,117 @@ Container( name="single_code_single_data_section", sections=[ - Section.Code(code=Op.ADDRESS + Op.POP + Op.STOP, code_outputs=128, max_stack_height=1), + Section.Code( + code=Op.ADDRESS + Op.POP + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), Section.Data(data="0xef"), ], ), - # TODO this is the only valid code I managed to produce - # somehow if code is 00 byte it gets rejected - # also if max_stack_height and code_outputs are not set it gets rejected -] - -INVALID: List[Container] = [ + Container( + # EOF allows truncated data section + name="no_data_section_contents", + sections=[ + Section.Code(Op.STOP, code_outputs=NON_RETURNING_SECTION), + Section.Data(data="0x", custom_size=1), + ], + code="ef0001 010004 0200010001 040001 00 00800000 00", + ), + Container( + # EOF allows truncated data section + name="data_section_contents_incomplete", + sections=[ + Section.Code(Op.STOP, code_outputs=NON_RETURNING_SECTION), + Section.Data(data="0xAABBCC", custom_size=4), + ], + ), Container( name="max_code_sections", - sections=[Section.Code(Op.STOP)] * 1024, - # TODO type section construction probably failed, expected no exception here - validity_error=EOFException.INVALID_FIRST_SECTION_TYPE, + sections=[ + Section.Code( + Op.JUMPF[i + 1] if i < (MAX_CODE_SECTIONS - 1) else Op.STOP, + code_outputs=NON_RETURNING_SECTION, + ) + for i in range(MAX_CODE_SECTIONS) + ], + ), + Container( + name="max_code_sections_plus_data", + sections=[ + Section.Code( + Op.JUMPF[i + 1] if i < (MAX_CODE_SECTIONS - 1) else Op.STOP, + code_outputs=NON_RETURNING_SECTION, + ) + for i in range(MAX_CODE_SECTIONS) + ] + + [Section.Data(data="0x00")], ), + Container( + name="max_code_sections_plus_container", + sections=[ + Section.Code( + Op.JUMPF[i + 1] if i < (MAX_CODE_SECTIONS - 1) else Op.STOP, + code_outputs=NON_RETURNING_SECTION, + ) + for i in range(MAX_CODE_SECTIONS) + ] + + [ + Section.Container( + container=Container( + name="max_code_sections", + sections=[ + Section.Code( + Op.JUMPF[i + 1] if i < (MAX_CODE_SECTIONS - 1) else Op.STOP, + code_outputs=NON_RETURNING_SECTION, + ) + for i in range(MAX_CODE_SECTIONS) + ], + ) + ) + ], + ), + Container( + name="max_code_sections_plus_data_plus_container", + sections=[ + Section.Code( + Op.JUMPF[i + 1] if i < (MAX_CODE_SECTIONS - 1) else Op.STOP, + code_outputs=NON_RETURNING_SECTION, + ) + for i in range(MAX_CODE_SECTIONS) + ] + + [ + Section.Container( + container=Container( + name="max_code_sections", + sections=[ + Section.Code( + Op.JUMPF[i + 1] if i < (MAX_CODE_SECTIONS - 1) else Op.STOP, + code_outputs=NON_RETURNING_SECTION, + ) + for i in range(MAX_CODE_SECTIONS) + ], + ) + ) + ] + + [Section.Data(data="0x00")], + ), + # TODO: Add more valid scenarios +] + +INVALID: List[Container] = [ Container( name="single_code_section_no_data_section", sections=[ - Section.Code(Op.STOP), + Section.Code(Op.STOP, code_outputs=NON_RETURNING_SECTION), ], - # TODO the exception must be about missing data section - validity_error=EOFException.INVALID_FIRST_SECTION_TYPE, + auto_data_section=False, + validity_error=EOFException.MISSING_DATA_SECTION, ), Container( name="incomplete_magic", raw_bytes=bytes([0xEF]), - validity_error=EOFException.INCOMPLETE_MAGIC, + validity_error=EOFException.INVALID_MAGIC, ), Container( name="no_version", @@ -92,12 +177,6 @@ raw_bytes=bytes([0xEF, 0x00, 0x01, 0x01, 0x00, 0x04, 0x02, 0x00, 0x01, 0x00]), validity_error=EOFException.INCOMPLETE_SECTION_SIZE, ), - Container( - name="no_data_section", - raw_bytes=bytes([0xEF, 0x00, 0x01, 0x01, 0x00, 0x04, 0x02, 0x00, 0x01, 0x00, 0x00]), - # TODO the exception must be about data section - validity_error=EOFException.ZERO_SECTION_SIZE, - ), Container( name="no_data_section_size", raw_bytes=bytes( @@ -113,7 +192,7 @@ 0x01, 0x00, 0x00, - 0x03, + 0x04, ] ), # TODO it looks like data section is missing or section header of type 0x00 @@ -150,31 +229,31 @@ Container( name="invalid_magic_01", magic=b"\xef\x01", - sections=[Section.Code(Op.STOP)], + sections=[Section.Code(Op.STOP, code_outputs=NON_RETURNING_SECTION)], validity_error=EOFException.INVALID_MAGIC, ), Container( name="invalid_magic_ff", magic=b"\xef\xFF", - sections=[Section.Code(Op.STOP)], + sections=[Section.Code(Op.STOP, code_outputs=NON_RETURNING_SECTION)], validity_error=EOFException.INVALID_MAGIC, ), Container( name="invalid_version_zero", version=b"\x00", - sections=[Section.Code(Op.STOP)], + sections=[Section.Code(Op.STOP, code_outputs=NON_RETURNING_SECTION)], validity_error=EOFException.INVALID_VERSION, ), Container( name="invalid_version_plus_one", version=int.to_bytes(LATEST_EOF_VERSION + 1, length=1, byteorder="big"), - sections=[Section.Code(Op.STOP)], + sections=[Section.Code(Op.STOP, code_outputs=NON_RETURNING_SECTION)], validity_error=EOFException.INVALID_VERSION, ), Container( name="invalid_version_high", version=b"\xFF", - sections=[Section.Code(Op.STOP)], + sections=[Section.Code(Op.STOP, code_outputs=NON_RETURNING_SECTION)], validity_error=EOFException.INVALID_VERSION, ), Container( @@ -188,7 +267,13 @@ ), Container( name="too_many_code_sections", - sections=[Section.Code(Op.STOP)] * 1025, + sections=[ + Section.Code( + Op.JUMPF[i + 1] if i < MAX_CODE_SECTIONS else Op.STOP, + code_outputs=NON_RETURNING_SECTION, + ) + for i in range(MAX_CODE_SECTIONS + 1) + ], validity_error=EOFException.TOO_MANY_CODE_SECTIONS, ), Container( @@ -223,45 +308,45 @@ Container( name="no_section_terminator_1", header_terminator=bytes(), - sections=[Section.Code(code=Op.STOP, custom_size=2)], + sections=[Section.Code(code=Op.STOP, custom_size=2, code_outputs=NON_RETURNING_SECTION)], # TODO the exception must be about terminator validity_error=EOFException.INVALID_SECTION_BODIES_SIZE, ), Container( name="no_section_terminator_2", header_terminator=bytes(), - sections=[Section.Code(code="0x", custom_size=3)], + sections=[Section.Code(code="0x", custom_size=3, code_outputs=NON_RETURNING_SECTION)], # TODO the exception must be about terminator validity_error=EOFException.INVALID_SECTION_BODIES_SIZE, ), Container( name="no_section_terminator_3", header_terminator=bytes(), - sections=[Section.Code(code=Op.PUSH1(0) + Op.STOP)], + sections=[Section.Code(code=Op.PUSH1(0) + Op.STOP, code_outputs=NON_RETURNING_SECTION)], # TODO the exception must be about terminator validity_error=EOFException.INVALID_SECTION_BODIES_SIZE, ), Container( name="no_code_section_contents", - sections=[Section.Code(code="0x", custom_size=0x01)], + sections=[Section.Code(code="0x", custom_size=0x01, code_outputs=NON_RETURNING_SECTION)], validity_error=EOFException.INVALID_SECTION_BODIES_SIZE, ), Container( name="incomplete_code_section_contents", sections=[ - Section.Code(code=Op.STOP, custom_size=0x02), + Section.Code(code=Op.STOP, custom_size=0x02, code_outputs=NON_RETURNING_SECTION), ], validity_error=EOFException.INVALID_SECTION_BODIES_SIZE, ), Container( name="trailing_bytes_after_code_section", - sections=[Section.Code(code=Op.PUSH1(0) + Op.STOP)], + sections=[Section.Code(code=Op.PUSH1(0) + Op.STOP, code_outputs=NON_RETURNING_SECTION)], extra=bytes([0xDE, 0xAD, 0xBE, 0xEF]), validity_error=EOFException.INVALID_SECTION_BODIES_SIZE, ), Container( name="empty_code_section", - sections=[Section.Code(code="0x")], + sections=[Section.Code(code="0x", code_outputs=NON_RETURNING_SECTION)], # TODO the exception must be about code section EOFException.INVALID_CODE_SECTION, validity_error=EOFException.ZERO_SECTION_SIZE, ), @@ -277,11 +362,12 @@ Container( name="data_section_preceding_code_section", auto_data_section=False, + auto_sort_sections=AutoSection.NONE, sections=[ Section.Data(data="0xDEADBEEF"), - Section.Code(Op.STOP), + Section.Code(Op.STOP, code_outputs=NON_RETURNING_SECTION), ], - validity_error=EOFException.INVALID_FIRST_SECTION_TYPE, + validity_error=EOFException.MISSING_CODE_HEADER, ), Container( name="data_section_without_code_section", @@ -292,7 +378,12 @@ Container( name="no_section_terminator_3a", header_terminator=bytes(), - sections=[Section.Code(code="0x030004")], + sections=[ + Section.Code( + code="0x030004", + code_outputs=NON_RETURNING_SECTION, + ) + ], # TODO the exception must be about terminator validity_error=EOFException.INVALID_SECTION_BODIES_SIZE, ), @@ -300,28 +391,11 @@ name="no_section_terminator_4a", header_terminator=bytes(), sections=[ - Section.Code(Op.STOP), + Section.Code(Op.STOP, code_outputs=NON_RETURNING_SECTION), Section.Data(data="0xAABBCCDD"), ], - # TODO the exception must be about terminator - validity_error=EOFException.INVALID_SECTION_BODIES_SIZE, - ), - Container( - name="no_data_section_contents", - sections=[ - Section.Code(Op.STOP), - Section.Data(data="0x", custom_size=1), - ], - # TODO: maybe it should detect that it is the data body that is wrong - validity_error=EOFException.INVALID_SECTION_BODIES_SIZE, - ), - Container( - name="data_section_contents_incomplete", - sections=[ - Section.Code(Op.STOP), - Section.Data(data="0xAABBCC", custom_size=4), - ], - validity_error=EOFException.INVALID_SECTION_BODIES_SIZE, + # TODO: The error of this validation can be random. + validity_error=EOFException.INVALID_FIRST_SECTION_TYPE, ), Container( name="trailing_bytes_after_data_section", @@ -345,8 +419,8 @@ Container( name="multiple_code_and_data_sections_1", sections=[ - Section.Code(Op.STOP), - Section.Code(Op.STOP), + Section.Code(Op.STOP, code_outputs=NON_RETURNING_SECTION), + Section.Code(Op.STOP, code_outputs=NON_RETURNING_SECTION), Section.Data(data="0xAA"), Section.Data(data="0xAA"), ], @@ -355,26 +429,17 @@ Container( name="multiple_code_and_data_sections_2", sections=[ - Section.Code(Op.STOP), + Section.Code(Op.STOP, code_outputs=NON_RETURNING_SECTION), Section.Data(data="0xAA"), - Section.Code(Op.STOP), + Section.Code(Op.STOP, code_outputs=NON_RETURNING_SECTION), Section.Data(data="0xAA"), ], validity_error=EOFException.MISSING_TERMINATOR, ), - Container( - name="code_section_out_of_order", - sections=[ - Section.Code(Op.STOP), - Section.Data(data="0xAA"), - Section.Code(Op.STOP), - ], - validity_error=EOFException.INVALID_FIRST_SECTION_TYPE, - ), Container( name="unknown_section_1", sections=[ - Section.Code(Op.STOP), + Section.Code(Op.STOP, code_outputs=NON_RETURNING_SECTION), Section.Data(data="0x"), Section(kind=VERSION_MAX_SECTION_KIND + 1, data="0x01"), ], @@ -385,7 +450,7 @@ sections=[ Section(kind=VERSION_MAX_SECTION_KIND + 1, data="0x01"), Section.Data(data="0x"), - Section.Code(Op.STOP), + Section.Code(Op.STOP, code_outputs=NON_RETURNING_SECTION), ], # TODO the exception should be about unknown section definition validity_error=EOFException.MISSING_TERMINATOR, @@ -393,7 +458,7 @@ Container( name="unknown_section_empty", sections=[ - Section.Code(Op.STOP), + Section.Code(Op.STOP, code_outputs=NON_RETURNING_SECTION), Section.Data(data="0x"), Section(kind=VERSION_MAX_SECTION_KIND + 1, data="0x"), ], @@ -413,7 +478,7 @@ sections=[ Section(kind=Kind.TYPE, data="0x00000000"), Section(kind=Kind.TYPE, data="0x00000000"), - Section.Code(Op.STOP), + Section.Code(Op.STOP, code_outputs=NON_RETURNING_SECTION), ], auto_type_section=AutoSection.NONE, validity_error=EOFException.MISSING_CODE_HEADER, @@ -422,38 +487,38 @@ name="empty_type_section", sections=[ Section(kind=Kind.TYPE, data="0x"), - Section.Code(Op.STOP), + Section.Code(Op.STOP, code_outputs=NON_RETURNING_SECTION), ], auto_type_section=AutoSection.NONE, - # TODO the exception must be about type section EOFException.INVALID_TYPE_SIZE, + # TODO the exception must be about type section EOFException.INVALID_TYPE_SECTION_SIZE, validity_error=EOFException.ZERO_SECTION_SIZE, ), Container( name="type_section_too_small_1", sections=[ Section(kind=Kind.TYPE, data="0x00"), - Section.Code(Op.STOP), + Section.Code(Op.STOP, code_outputs=NON_RETURNING_SECTION), ], auto_type_section=AutoSection.NONE, - validity_error=EOFException.INVALID_TYPE_SIZE, + validity_error=EOFException.INVALID_TYPE_SECTION_SIZE, ), Container( name="type_section_too_small_2", sections=[ Section(kind=Kind.TYPE, data="0x000000"), - Section.Code(Op.STOP), + Section.Code(Op.STOP, code_outputs=NON_RETURNING_SECTION), ], auto_type_section=AutoSection.NONE, - validity_error=EOFException.INVALID_TYPE_SIZE, + validity_error=EOFException.INVALID_TYPE_SECTION_SIZE, ), Container( name="type_section_too_big", sections=[ Section(kind=Kind.TYPE, data="0x0000000000"), - Section.Code(Op.STOP), + Section.Code(Op.STOP, code_outputs=NON_RETURNING_SECTION), ], auto_type_section=AutoSection.NONE, - validity_error=EOFException.INVALID_TYPE_SIZE, + validity_error=EOFException.INVALID_TYPE_SECTION_SIZE, ), ] @@ -463,9 +528,7 @@ EIP-4750 Valid and Invalid Containers """ -VALID += [] - -INVALID += [ +VALID += [ Container( name="single_code_section_max_stack_size", sections=[ @@ -474,12 +537,10 @@ + (Op.POP * MAX_OPERAND_STACK_HEIGHT) + Op.STOP, code_inputs=0, - code_outputs=0, + code_outputs=NON_RETURNING_SECTION, max_stack_height=MAX_OPERAND_STACK_HEIGHT, ), ], - # TODO check the types section construction, this test was supposed to be valid - validity_error=EOFException.INVALID_FIRST_SECTION_TYPE, ), Container( name="single_code_section_input_maximum", @@ -487,7 +548,7 @@ Section.Code( code=((Op.PUSH0 * MAX_CODE_INPUTS) + Op.CALLF[1] + Op.STOP), code_inputs=0, - code_outputs=0, + code_outputs=NON_RETURNING_SECTION, max_stack_height=MAX_CODE_INPUTS, ), Section.Code( @@ -497,8 +558,6 @@ max_stack_height=MAX_CODE_INPUTS, ), ], - # TODO check the types section construction, this test was supposed to be valid - validity_error=EOFException.INVALID_FIRST_SECTION_TYPE, ), Container( name="single_code_section_output_maximum", @@ -506,7 +565,7 @@ Section.Code( code=(Op.CALLF[1] + Op.STOP), code_inputs=0, - code_outputs=0, + code_outputs=NON_RETURNING_SECTION, max_stack_height=MAX_CODE_OUTPUTS, ), Section.Code( @@ -516,13 +575,15 @@ max_stack_height=MAX_CODE_OUTPUTS, ), ], - # TODO check the types section construction, this test was supposed to be valid - validity_error=EOFException.INVALID_FIRST_SECTION_TYPE, ), Container( name="multiple_code_section_max_inputs_max_outputs", sections=[ - Section.Code(Op.STOP), + Section.Code( + (Op.PUSH0 * MAX_CODE_OUTPUTS) + Op.CALLF[1] + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=MAX_CODE_OUTPUTS, + ), Section.Code( code=Op.RETF, code_inputs=MAX_CODE_INPUTS, @@ -530,41 +591,29 @@ max_stack_height=MAX_CODE_INPUTS, ), ], - # TODO check the types section construction, this test was supposed to be valid - validity_error=EOFException.INVALID_FIRST_SECTION_TYPE, - ), - Container( - name="max_code_sections_1024", - sections=[Section.Code(Op.STOP)] * 1024, - # TODO check the types section construction, this test was supposed to be valid - validity_error=EOFException.INVALID_FIRST_SECTION_TYPE, - ), - Container( - name="max_code_sections_1024_and_data", - sections=([Section.Code(Op.STOP)] * 1024) - + [ - Section.Data("0x00"), - ], - # TODO check the types section construction, this test was supposed to be valid - validity_error=EOFException.INVALID_FIRST_SECTION_TYPE, ), +] + +INVALID += [ Container( name="single_code_section_non_zero_inputs", - sections=[Section.Code(code=Op.POP, code_inputs=1)], + sections=[ + Section.Code(code=Op.POP + Op.RETF, code_inputs=1, code_outputs=NON_RETURNING_SECTION) + ], # TODO the exception must be about code or non, cause it looks legit validity_error=EOFException.INVALID_FIRST_SECTION_TYPE, ), Container( name="single_code_section_non_zero_outputs", - sections=[Section.Code(code=Op.PUSH0, code_outputs=1)], + sections=[Section.Code(code=Op.PUSH0 + Op.RETF, code_outputs=1)], # TODO the exception must be about code or non, cause it looks legit validity_error=EOFException.INVALID_FIRST_SECTION_TYPE, ), Container( name="multiple_code_section_non_zero_inputs", sections=[ - Section.Code(code=Op.POP, code_inputs=1), - Section.Code(Op.STOP), + Section.Code(code=Op.POP + Op.RETF, code_inputs=1, code_outputs=NON_RETURNING_SECTION), + Section.Code(Op.STOP, code_outputs=NON_RETURNING_SECTION), ], # TODO the actual exception should be EOFException.INVALID_TYPE_BODY, validity_error=EOFException.INVALID_FIRST_SECTION_TYPE, @@ -573,7 +622,7 @@ name="multiple_code_section_non_zero_outputs", sections=[ Section.Code(code=Op.PUSH0, code_outputs=1), - Section.Code(Op.STOP), + Section.Code(Op.STOP, code_outputs=NON_RETURNING_SECTION), ], # TODO the actual exception should be EOFException.INVALID_TYPE_BODY, validity_error=EOFException.INVALID_FIRST_SECTION_TYPE, @@ -582,7 +631,7 @@ name="data_section_before_code_with_type", sections=[ Section.Data(data="0xAA"), - Section.Code(Op.STOP), + Section.Code(Op.STOP, code_outputs=NON_RETURNING_SECTION), ], auto_sort_sections=AutoSection.NONE, validity_error=EOFException.MISSING_CODE_HEADER, @@ -591,29 +640,24 @@ name="data_section_listed_in_type", sections=[ Section.Data(data="0x00", force_type_listing=True), - Section.Code(Op.STOP), + Section.Code(Op.STOP, code_outputs=NON_RETURNING_SECTION), ], - validity_error=EOFException.INVALID_TYPE_SIZE, - ), - Container( - name="code_sections_above_1024", - sections=[Section.Code(Op.STOP)] * 1025, - validity_error=EOFException.TOO_MANY_CODE_SECTIONS, + validity_error=EOFException.INVALID_TYPE_SECTION_SIZE, ), Container( name="single_code_section_incomplete_type", sections=[ Section(kind=Kind.TYPE, data="0x00"), - Section.Code(Op.STOP), + Section.Code(Op.STOP, code_outputs=NON_RETURNING_SECTION), ], auto_type_section=AutoSection.NONE, - validity_error=EOFException.INVALID_TYPE_SIZE, + validity_error=EOFException.INVALID_TYPE_SECTION_SIZE, ), Container( name="single_code_section_incomplete_type_2", sections=[ Section(kind=Kind.TYPE, data="0x00", custom_size=2), - Section.Code(Op.STOP), + Section.Code(Op.STOP, code_outputs=NON_RETURNING_SECTION), ], validity_error=EOFException.INVALID_SECTION_BODIES_SIZE, ), @@ -623,7 +667,7 @@ Section.Code( code=((Op.PUSH0 * (MAX_CODE_INPUTS + 1)) + Op.CALLF[1] + Op.STOP), code_inputs=0, - code_outputs=0, + code_outputs=NON_RETURNING_SECTION, max_stack_height=(MAX_CODE_INPUTS + 1), ), Section.Code( @@ -634,7 +678,7 @@ ), ], # TODO auto types section generation probably failed. the exception must be about code - validity_error=EOFException.INVALID_FIRST_SECTION_TYPE, + validity_error=EOFException.INPUTS_OUTPUTS_NUM_ABOVE_LIMIT, ), Container( name="single_code_section_output_too_large", @@ -642,18 +686,18 @@ Section.Code( code=(Op.CALLF[1] + Op.STOP), code_inputs=0, - code_outputs=0, - max_stack_height=(MAX_CODE_OUTPUTS + 1), + code_outputs=NON_RETURNING_SECTION, + max_stack_height=(MAX_CODE_OUTPUTS + 2), ), Section.Code( - code=(Op.PUSH0 * (MAX_CODE_OUTPUTS + 1)) + Op.RETF, + code=(Op.PUSH0 * (MAX_CODE_OUTPUTS + 2)) + Op.RETF, code_inputs=0, - code_outputs=(MAX_CODE_OUTPUTS + 1), + code_outputs=(MAX_CODE_OUTPUTS + 2), max_stack_height=(MAX_CODE_OUTPUTS + 1), ), ], # TODO the exception must be about code body - validity_error=EOFException.INVALID_FIRST_SECTION_TYPE, + validity_error=EOFException.INPUTS_OUTPUTS_NUM_ABOVE_LIMIT, ), Container( name="single_code_section_max_stack_size_too_large", @@ -661,11 +705,11 @@ Section.Code( code=Op.CALLER * 1024 + Op.POP * 1024 + Op.STOP, code_inputs=0, - code_outputs=0, + code_outputs=NON_RETURNING_SECTION, max_stack_height=1024, ), ], # TODO auto types section generation probably failed, the exception must be about code - validity_error=EOFException.INVALID_FIRST_SECTION_TYPE, + validity_error=EOFException.MAX_STACK_HEIGHT_ABOVE_LIMIT, ), ] diff --git a/tests/prague/eip3540_eof_v1/opcodes.py b/tests/prague/eip7692_eof_v1/eip3540_eof_v1/opcodes.py similarity index 100% rename from tests/prague/eip3540_eof_v1/opcodes.py rename to tests/prague/eip7692_eof_v1/eip3540_eof_v1/opcodes.py diff --git a/tests/prague/eip7480_data_section/spec.py b/tests/prague/eip7692_eof_v1/eip3540_eof_v1/spec.py similarity index 66% rename from tests/prague/eip7480_data_section/spec.py rename to tests/prague/eip7692_eof_v1/eip3540_eof_v1/spec.py index 7bf760554f..3b477ba776 100644 --- a/tests/prague/eip7480_data_section/spec.py +++ b/tests/prague/eip7692_eof_v1/eip3540_eof_v1/spec.py @@ -1,5 +1,3 @@ """ EOF V1 Constants used throughout all tests """ - -EOF_FORK_NAME = "Prague" diff --git a/tests/prague/eip3540_eof_v1/test_code_validation.py b/tests/prague/eip7692_eof_v1/eip3540_eof_v1/test_code_validation.py similarity index 98% rename from tests/prague/eip3540_eof_v1/test_code_validation.py rename to tests/prague/eip7692_eof_v1/eip3540_eof_v1/test_code_validation.py index f0bb8b8d36..95bedf716f 100644 --- a/tests/prague/eip3540_eof_v1/test_code_validation.py +++ b/tests/prague/eip7692_eof_v1/eip3540_eof_v1/test_code_validation.py @@ -14,10 +14,12 @@ EOFTestFiller, TestAddress, Transaction, - compute_create3_address, + compute_eofcreate_address, ) from ethereum_test_tools.eof.v1 import Container, Initcode +from .. import EOF_FORK_NAME + # from .code_validation import INVALID as INVALID_CODE # from .code_validation import VALID as VALID_CODE # from .code_validation_function import INVALID as INVALID_FN @@ -26,7 +28,6 @@ # from .code_validation_jump import VALID as VALID_RJUMP from .container import INVALID as INVALID_CONTAINERS from .container import VALID as VALID_CONTAINERS -from .spec import EOF_FORK_NAME # from .tests_execution_function import VALID as VALID_EXEC_FN @@ -103,7 +104,7 @@ def post( # noqa: D103 container: Container, create3_opcode_contract_address: str, ) -> Dict[Address, Account]: - create_opcode_created_contract_address = compute_create3_address( + create_opcode_created_contract_address = compute_eofcreate_address( create3_opcode_contract_address, 0, bytes(create3_init_container.init_container), diff --git a/tests/prague/eip3540_eof_v1/test_eof_example.py b/tests/prague/eip7692_eof_v1/eip3540_eof_v1/test_eof_example.py similarity index 82% rename from tests/prague/eip3540_eof_v1/test_eof_example.py rename to tests/prague/eip7692_eof_v1/eip3540_eof_v1/test_eof_example.py index 72f08683d0..4be4c19c20 100644 --- a/tests/prague/eip3540_eof_v1/test_eof_example.py +++ b/tests/prague/eip7692_eof_v1/eip3540_eof_v1/test_eof_example.py @@ -6,10 +6,16 @@ from ethereum_test_tools import EOFTestFiller from ethereum_test_tools import Opcodes as Op -from ethereum_test_tools.eof.v1 import AutoSection, Container, Section +from ethereum_test_tools.eof.v1 import ( + AutoSection, + BytesConvertible, + Container, + EOFException, + Section, +) from ethereum_test_tools.eof.v1.constants import NON_RETURNING_SECTION -from .spec import EOF_FORK_NAME +from .. import EOF_FORK_NAME REFERENCE_SPEC_GIT_PATH = "EIPS/eip-3540.md" REFERENCE_SPEC_VERSION = "8dcb0a8c1c0102c87224308028632cc986a61183" @@ -125,3 +131,40 @@ def test_eof_example_custom_fields(eof_test: EOFTestFiller): data=eof_code, expect_exception=eof_code.validity_error, ) + + +@pytest.mark.parametrize( + "data_section_bytes", + ("0x01", "0xef"), +) +@pytest.mark.parametrize( + "code_section_code, exception", + [(Op.PUSH1(10) + Op.STOP, None), (Op.PUSH1(14), EOFException.MISSING_STOP_OPCODE)], +) +def test_eof_example_parameters( + eof_test: EOFTestFiller, + data_section_bytes: BytesConvertible, + code_section_code: BytesConvertible, + exception: EOFException, +): + """ + Example of python EOF classes + """ + eof_code = Container( + name="parametrized_eof_example", + sections=[ + Section.Code( + code=code_section_code, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data(data_section_bytes), + ], + validity_error=exception, + ) + + eof_test( + data=eof_code, + expect_exception=eof_code.validity_error, + ) diff --git a/tests/prague/eip7692_eof_v1/eip3540_eof_v1/test_example_valid_invalid.py b/tests/prague/eip7692_eof_v1/eip3540_eof_v1/test_example_valid_invalid.py new file mode 100644 index 0000000000..1145be16e9 --- /dev/null +++ b/tests/prague/eip7692_eof_v1/eip3540_eof_v1/test_example_valid_invalid.py @@ -0,0 +1,508 @@ +""" +EOF Classes example use +""" + +import pytest + +from ethereum_test_tools import EOFTestFiller, Opcode +from ethereum_test_tools import Opcodes as Op +from ethereum_test_tools.eof.v1 import Bytes, Container, EOFException, Section +from ethereum_test_tools.eof.v1.constants import NON_RETURNING_SECTION + +from .. import EOF_FORK_NAME + +REFERENCE_SPEC_GIT_PATH = "EIPS/eip-3540.md" +REFERENCE_SPEC_VERSION = "8dcb0a8c1c0102c87224308028632cc986a61183" + +pytestmark = pytest.mark.valid_from(EOF_FORK_NAME) + + +@pytest.mark.parametrize( + "eof_code,expected_hex_bytecode,exception", + [ + pytest.param( + # Check that simple EOF1 deploys + Container( + name="EOF1V0001", + sections=[ + Section.Code( + code=Op.ADDRESS + Op.POP + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data("0xef"), + ], + ), + "ef000101000402000100030400010000800001305000ef", + None, + id="simple_eof_1_deploy", + ), + pytest.param( + # Check that EOF1 undersize data is ok (4 declared, 2 provided) + # https://github.com/ipsilon/eof/blob/main/spec/eof.md#data-section-lifecycle + Container( + name="EOF1V0016", + sections=[ + Section.Code( + code=Op.ADDRESS + Op.POP + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data("0x0bad", custom_size=4), + ], + ), + "ef0001010004020001000304000400008000013050000bad", + None, + id="undersize_data_ok", + ), + pytest.param( + # Check that EOF1 with too many or too few bytes fails + Container( + name="EOF1I0006", + sections=[ + Section.Code( + code=Op.ADDRESS + Op.POP + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data("0x0bad60A70BAD", custom_size=4), + ], + ), + "ef0001010004020001000304000400008000013050000bad60A70BAD", + EOFException.INVALID_SECTION_BODIES_SIZE, + id="oversize_data_fail", + ), + pytest.param( + # Check that data section size is valid + Container( + name="EOF1V0001", + sections=[ + Section.Code( + code=Op.ADDRESS + Op.POP + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data("0x0bad60A7"), + ], + ), + "ef0001010004020001000304000400008000013050000bad60A7", + None, + id="data_ok", + ), + pytest.param( + # Check that EOF1 with an illegal opcode fails + Container( + name="EOF1I0008", + sections=[ + Section.Code( + code=Op.ADDRESS + Opcode(0xEF) + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data("0x0bad60A7"), + ], + ), + "ef00010100040200010003040004000080000130ef000bad60A7", + EOFException.UNDEFINED_INSTRUCTION, + id="illegal_opcode_fail", + ), + pytest.param( + # Check that valid EOF1 can include 0xFE, the designated invalid opcode + Container( + name="EOF1V0004", + sections=[ + Section.Code( + code=Op.ADDRESS + Op.POP + Op.INVALID, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data("0x0bad60A7"), + ], + ), + "ef0001010004020001000304000400008000013050fe0bad60A7", + None, + id="fe_opcode_ok", + ), + pytest.param( + # Check that EOF1 with a bad end of sections number fails + Container( + name="EOF1I0005", + sections=[ + Section.Code( + code=Op.ADDRESS + Op.POP + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data("0xef"), + ], + header_terminator=Bytes(b"\xFF"), + ), + "ef00010100040200010003040001ff00800001305000ef", + EOFException.MISSING_TERMINATOR, + id="headers_terminator_invalid", + ), + pytest.param( + # Check that code that uses a new style relative jump succeeds + Container( + name="EOF1V0008", + sections=[ + Section.Code( + code=Op.PUSH0 + + Op.RJUMPI[3] + + Op.RJUMP[3] + + Op.RJUMP[3] + + Op.RJUMP[-6] + + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data("0x0bad60A7"), + ], + ), + "ef0001010004020001000E04000400008000015FE10003E00003E00003E0FFFA000bad60A7", + None, + id="rjump_valid", + ), + pytest.param( + # Sections with unreachable code fail + Container( + name="EOF1I0023", + sections=[ + Section.Code( + code=Op.RJUMP[1] + Op.NOOP + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=0, + ), + Section.Data("0x0bad60A7"), + ], + ), + "ef000101000402000100050400040000800000E000015B000bad60A7", + EOFException.UNREACHABLE_INSTRUCTIONS, + id="unreachable_code", + ), + pytest.param( + # Check that code that uses a new style conditional jump succeeds + Container( + name="EOF1V0011", + sections=[ + Section.Code( + code=Op.PUSH1(1) + Op.RJUMPI[1] + Op.NOOP + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data("0x0bad60A7"), + ], + ), + "ef0001010004020001000704000400008000016001E100015B000bad60A7", + None, + id="rjumpi_valid", + ), + pytest.param( + # Sections that end with a legit terminating opcode are OK + Container( + name="EOF1V0014", + sections=[ + Section.Code( + code=Op.PUSH0 + + Op.CALLDATALOAD + + Op.RJUMPV[0, 3, 6, 9] + + Op.JUMPF[1] + + Op.JUMPF[2] + + Op.JUMPF[3] + + Op.CALLF[4] + + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Code( + code=Op.PUSH0 + Op.PUSH0 + Op.RETURN, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ), + Section.Code( + code=Op.PUSH0 + Op.PUSH0 + Op.REVERT, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ), + Section.Code( + code=Op.INVALID, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=0, + ), + Section.Code( + code=Op.RETF, + code_inputs=0, + code_outputs=0, + max_stack_height=0, + ), + Section.Data("0x0bad60A7"), + ], + ), + "EF0001010014020005001900030003000100010400040000800001008000020080000200800000000" + "000005f35e2030000000300060009e50001e50002e50003e30004005f5ff35f5ffdfee40bad60a7", + None, + id="rjumpv_section_terminator_valid", + ), + pytest.param( + # Check that jump tables work + Container( + name="EOF1V0013", + sections=[ + Section.Code( + code=Op.PUSH1(1) + + Op.RJUMPV[2, 0] + + Op.ADDRESS + + Op.POP + + Op.ADDRESS + + Op.POP + + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data("0x0bad60A7"), + ], + ), + "ef0001010004020001000D04000400008000016001E2010002000030503050000bad60A7", + None, + id="jump_tables_valid", + ), + pytest.param( + # Check that jumps into the middle on an opcode are not allowed + Container( + name="EOF1I0019", + sections=[ + Section.Code( + code=Op.PUSH1(1) + + Op.RJUMPV[b"\x02\x00\x02\xFF\xFF"] + + Op.ADDRESS + + Op.POP + + Op.ADDRESS + + Op.POP + + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data("0x0bad60A7"), + ], + ), + "ef0001010004020001000D04000400008000016001E2020002FFFF30503050000bad60A7", + EOFException.INVALID_RJUMP_DESTINATION, + id="rjump_invalid", + ), + pytest.param( + # TODO why here is expected an exception by the comment but test is valid + # Check that you can't get to the same opcode with two different stack heights + Container( + name="EOF1I0020", + sections=[ + Section.Code( + code=Op.PUSH1(1) + Op.RJUMPI[1] + Op.ADDRESS + Op.NOOP + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data("0x0bad60A7"), + ], + ), + "ef0001010004020001000804000400008000016001E10001305B000bad60A7", + None, + id="jump_to_opcode_ok", + ), + pytest.param( + # Check that jumps into the middle on an opcode are not allowed + Container( + name="EOF1I0019", + sections=[ + Section.Code( + code=Op.RJUMP[3] + Op.RJUMP[2] + Op.RJUMP[-6] + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=0, + ), + Section.Data("0x0bad60A7"), + ], + ), + "ef0001010004020001000A0400040000800000E00003E00002E0FFFA000bad60A7", + EOFException.INVALID_RJUMP_DESTINATION, + id="rjump_3_2_m6_fails", + ), + pytest.param( + # Check that jumps into the middle on an opcode are not allowed + Container( + name="EOF1I0019", + sections=[ + Section.Code( + code=Op.PUSH1(0) + + Op.PUSH1(0) + + Op.PUSH1(0) + + Op.RJUMPI[3] + + Op.RJUMPI[2] + + Op.RJUMPI[-6] + + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=3, + ), + Section.Data("0x0bad60A7"), + ], + ), + "ef000101000402000100100400040000800003600060006000E10003E10002E1FFFA000bad60A7", + EOFException.INVALID_RJUMP_DESTINATION, + id="push1_0_0_0_rjump_3_2_m6_fails", + ), + pytest.param( + # Check that that code that uses removed opcodes fails + Container( + name="EOF1I0015", + sections=[ + Section.Code( + code=Op.PUSH1(3) + Op.JUMP + Op.JUMPDEST + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data("0xef"), + ], + ), + "ef0001010004020001000504000100008000016003565B00ef", + EOFException.UNDEFINED_INSTRUCTION, + id="jump_jumpdest_fails", + ), + ], +) +def test_example_valid_invalid( + eof_test: EOFTestFiller, + eof_code: Container, + expected_hex_bytecode: str, + exception: EOFException | None, +): + """ + Verify eof container construction and exception + """ + # TODO remove this after Container class implementation is reliable + assert bytes(eof_code).hex() == bytes.fromhex(expected_hex_bytecode).hex() + + eof_test( + data=eof_code, + expect_exception=exception, + ) + + +@pytest.mark.parametrize( + "skip_header_listing, skip_body_listing, skip_types_body_listing, skip_types_header_listing," + "expected_code, expected_exception", + [ + ( + # Data 16 test case of valid invalid eof ori filler + True, # second section is not in code header array + True, # second section is not in container's body (it's code bytes) + False, # but it's code input bytes still listed in container's body + False, # but it's code input bytes size still added to types section size + "ef000101000802000100030400040000800001000000003050000bad60A7", + EOFException.INVALID_TYPE_SECTION_SIZE, + ), + ( + True, # second section is not in code header array + False, # second section code is in container's body (3050000) + False, # but it's code input bytes still listed in container's body + False, # but it's code input bytes size still added to types section size + "ef000101000802000100030400040000800001000000003050003050000bad60A7", + EOFException.INVALID_SECTION_BODIES_SIZE, + ), + ( + False, # second section is mentioned in code header array (0003) + True, # second section is not in container's body (it's code bytes) + False, # but it's code input bytes still listed in container's body + False, # but it's code input bytes size still added to types section size + "ef0001010008020002000300030400040000800001000000003050000bad60A7", + EOFException.UNREACHABLE_CODE_SECTIONS, + ), + ( + False, # second section is mentioned in code header array (0003) + False, # second section code is in container's body (3050000) + False, # but it's code input bytes still listed in container's body + False, # but it's code input bytes size still added to types section size + "ef0001010008020002000300030400040000800001000000003050003050000bad60A7", + EOFException.UNREACHABLE_CODE_SECTIONS, + ), + ( + # Data 17 test case of valid invalid eof ori filler + True, # second section is not in code header array + True, # second section is not in container's body (it's code bytes) + True, # it's code input bytes are not listed in container's body (00000000) + False, # but it's code input bytes size still added to types section size + "ef0001010008020001000304000400008000013050000bad60a7", + EOFException.INVALID_TYPE_SECTION_SIZE, + ), + ( + True, # second section is not in code header array + True, # second section is not in container's body (it's code bytes) + True, # it's code input bytes are not listed in container's body (00000000) + True, # and it is bytes size is not counted in types header + "ef0001010004020001000304000400008000013050000bad60a7", + None, + ), + ], +) +def test_code_section_header_body_mismatch( + eof_test: EOFTestFiller, + skip_header_listing: bool, + skip_body_listing: bool, + skip_types_body_listing: bool, + skip_types_header_listing: bool, + expected_code: str, + expected_exception: EOFException | None, +): + """ + Inconsistent number of code sections (between types and code) + """ + eof_code = Container( + name="EOF1I0018", + sections=[ + Section.Code( + code=Op.ADDRESS + Op.POP + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Code( + code=Op.ADDRESS + Op.POP + Op.STOP, + code_inputs=0, + code_outputs=0, + max_stack_height=0, + # weather to not mention it in code section header list + skip_header_listing=skip_header_listing, + # weather to not print it's code in containers body + skip_body_listing=skip_body_listing, + # weather to not print it's input bytes in containers body + skip_types_body_listing=skip_types_body_listing, + # weather to not calculate it's input bytes size in types section's header + skip_types_header_listing=skip_types_header_listing, + ), + Section.Data("0x0bad60A7"), + ], + ) + + # TODO remove this after Container class implementation is reliable + assert bytes(eof_code).hex() == bytes.fromhex(expected_code).hex() + + eof_test( + data=eof_code, + expect_exception=expected_exception, + ) diff --git a/tests/prague/eip3540_eof_v1/test_execution_function.py b/tests/prague/eip7692_eof_v1/eip3540_eof_v1/test_execution_function.py similarity index 98% rename from tests/prague/eip3540_eof_v1/test_execution_function.py rename to tests/prague/eip7692_eof_v1/eip3540_eof_v1/test_execution_function.py index d30b3093f4..e660c1c2a8 100644 --- a/tests/prague/eip3540_eof_v1/test_execution_function.py +++ b/tests/prague/eip7692_eof_v1/eip3540_eof_v1/test_execution_function.py @@ -22,7 +22,7 @@ ) from ethereum_test_tools.vm.opcode import Opcodes as Op -from .spec import EOF_FORK_NAME +from .. import EOF_FORK_NAME REFERENCE_SPEC_GIT_PATH = "EIPS/eip-4750.md" REFERENCE_SPEC_VERSION = "90f716078d0b08ce508a1e57803f885cc2f2e15e" @@ -140,7 +140,7 @@ Section.Code( code=( Op.DUP1 - + Op.PUSH2(MAX_RETURN_STACK_HEIGHT - 1) + + Op.PUSH2(MAX_RETURN_STACK_HEIGHT) + Op.SUB + Op.RJUMPI[len(Op.POP) + len(Op.RETF)] + Op.POP @@ -170,7 +170,7 @@ Op.PUSH0 + Op.SLOAD + Op.DUP1 - + Op.PUSH2(MAX_RETURN_STACK_HEIGHT - 1) + + Op.PUSH2(MAX_RETURN_STACK_HEIGHT) + Op.SUB + Op.RJUMPI[len(Op.POP) + len(Op.STOP)] + Op.POP @@ -202,7 +202,7 @@ Op.PUSH0 + Op.MLOAD + Op.DUP1 - + Op.PUSH2(MAX_RETURN_STACK_HEIGHT - 1) + + Op.PUSH2(MAX_RETURN_STACK_HEIGHT) + Op.SUB + Op.RJUMPI[len(Op.POP) + len(Op.RETF)] + Op.POP @@ -279,7 +279,7 @@ Op.PUSH0 + Op.SLOAD + Op.DUP1 - + Op.PUSH2(MAX_RETURN_STACK_HEIGHT) + + Op.PUSH2(MAX_RETURN_STACK_HEIGHT + 1) + Op.SUB + Op.RJUMPI[len(Op.POP) + len(Op.RETF)] + Op.POP diff --git a/tests/prague/eip7692_eof_v1/eip6206_jumpf/__init__.py b/tests/prague/eip7692_eof_v1/eip6206_jumpf/__init__.py new file mode 100644 index 0000000000..6b3accdbb0 --- /dev/null +++ b/tests/prague/eip7692_eof_v1/eip6206_jumpf/__init__.py @@ -0,0 +1,3 @@ +""" +EOF tests for EIP-6206 JUMPF +""" diff --git a/tests/prague/eip7692_eof_v1/eip6206_jumpf/helpers.py b/tests/prague/eip7692_eof_v1/eip6206_jumpf/helpers.py new file mode 100644 index 0000000000..056278eca8 --- /dev/null +++ b/tests/prague/eip7692_eof_v1/eip6206_jumpf/helpers.py @@ -0,0 +1,13 @@ +""" +EOF JumpF tests helpers +""" +import itertools + +"""Storage addresses for common testing fields""" +_slot = itertools.count() +next(_slot) # don't use slot 0 +slot_code_worked = next(_slot) +slot_last_slot = next(_slot) + +"""Storage values for common testing fields""" +value_code_worked = 0x2015 diff --git a/tests/prague/eip3540_eof_v1/spec.py b/tests/prague/eip7692_eof_v1/eip6206_jumpf/spec.py similarity index 66% rename from tests/prague/eip3540_eof_v1/spec.py rename to tests/prague/eip7692_eof_v1/eip6206_jumpf/spec.py index 7bf760554f..3b477ba776 100644 --- a/tests/prague/eip3540_eof_v1/spec.py +++ b/tests/prague/eip7692_eof_v1/eip6206_jumpf/spec.py @@ -1,5 +1,3 @@ """ EOF V1 Constants used throughout all tests """ - -EOF_FORK_NAME = "Prague" diff --git a/tests/prague/eip7692_eof_v1/eip6206_jumpf/test_jumpf_execution.py b/tests/prague/eip7692_eof_v1/eip6206_jumpf/test_jumpf_execution.py new file mode 100644 index 0000000000..6208ee0100 --- /dev/null +++ b/tests/prague/eip7692_eof_v1/eip6206_jumpf/test_jumpf_execution.py @@ -0,0 +1,161 @@ +""" +EOF JUMPF tests covering simple cases. +""" +import pytest + +from ethereum_test_tools import Account, EOFException, EOFStateTestFiller +from ethereum_test_tools.eof.v1 import Container, Section +from ethereum_test_tools.eof.v1.constants import NON_RETURNING_SECTION +from ethereum_test_tools.vm.opcode import Opcodes as Op + +from .. import EOF_FORK_NAME +from .helpers import slot_code_worked, value_code_worked + +REFERENCE_SPEC_GIT_PATH = "EIPS/eip-6206.md" +REFERENCE_SPEC_VERSION = "2f365ea0cd58faa6e26013ea77ce6d538175f7d0" + +pytestmark = pytest.mark.valid_from(EOF_FORK_NAME) + + +def test_jumpf_forward( + eof_state_test: EOFStateTestFiller, +): + """Test JUMPF jumping forward""" + eof_state_test( + data=Container( + sections=[ + Section.Code( + code=Op.JUMPF[1], + code_outputs=NON_RETURNING_SECTION, + ), + Section.Code( + Op.SSTORE(slot_code_worked, value_code_worked) + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ), + ], + ), + container_post=Account(storage={slot_code_worked: value_code_worked}), + tx_data=b"\1", + ) + + +def test_jumpf_backward( + eof_state_test: EOFStateTestFiller, +): + """Tests JUMPF jumping backward""" + eof_state_test( + data=Container( + sections=[ + Section.Code( + code=Op.CALLF[2] + Op.SSTORE(slot_code_worked, value_code_worked) + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ), + Section.Code( + code=Op.RETF, + ), + Section.Code( + code=Op.JUMPF[1], + ), + ], + ), + container_post=Account(storage={slot_code_worked: value_code_worked}), + tx_data=b"\1", + ) + + +def test_jumpf_to_self( + eof_state_test: EOFStateTestFiller, +): + """Tests JUMPF jumping to self""" + eof_state_test( + data=Container( + sections=[ + Section.Code( + code=Op.SLOAD(slot_code_worked) + + Op.ISZERO + + Op.RJUMPI[1] + + Op.STOP + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.JUMPF[0], + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ) + ], + ), + container_post=Account(storage={slot_code_worked: value_code_worked}), + tx_data=b"\1", + ) + + +def test_jumpf_too_large( + eof_state_test: EOFStateTestFiller, +): + """Tests JUMPF jumping to a section outside the max section range""" + eof_state_test( + data=Container( + sections=[ + Section.Code( + code=Op.JUMPF[1025], + code_outputs=NON_RETURNING_SECTION, + ) + ], + validity_error=EOFException.UNDEFINED_EXCEPTION, + ), + ) + + +def test_jumpf_way_too_large( + eof_state_test: EOFStateTestFiller, +): + """Tests JUMPF jumping to uint64.MAX""" + eof_state_test( + data=Container( + sections=[ + Section.Code( + code=Op.JUMPF[0xFFFF], + code_outputs=NON_RETURNING_SECTION, + ) + ], + validity_error=EOFException.UNDEFINED_EXCEPTION, + ), + ) + + +def test_jumpf_to_nonexistent_section( + eof_state_test: EOFStateTestFiller, +): + """Tests JUMPF jumping to valid section number but where the section does not exist""" + eof_state_test( + data=Container( + sections=[ + Section.Code( + code=Op.JUMPF[5], + code_outputs=NON_RETURNING_SECTION, + ) + ], + validity_error=EOFException.UNDEFINED_EXCEPTION, + ), + ) + + +def test_callf_to_non_returning_section( + eof_state_test: EOFStateTestFiller, +): + """Tests CALLF into a non-returning section""" + eof_state_test( + data=Container( + sections=[ + Section.Code( + code=Op.CALLF[1], + code_outputs=NON_RETURNING_SECTION, + ), + Section.Code( + code=Op.STOP, + outputs=NON_RETURNING_SECTION, + ), + ], + validity_error=EOFException.MISSING_STOP_OPCODE, + ), + ) diff --git a/tests/prague/eip7692_eof_v1/eip6206_jumpf/test_jumpf_stack.py b/tests/prague/eip7692_eof_v1/eip6206_jumpf/test_jumpf_stack.py new file mode 100644 index 0000000000..7a6db5602f --- /dev/null +++ b/tests/prague/eip7692_eof_v1/eip6206_jumpf/test_jumpf_stack.py @@ -0,0 +1,140 @@ +""" +EOF JUMPF tests covering stack validation rules. +""" +import pytest + +from ethereum_test_tools import Account, EOFException, EOFStateTestFiller +from ethereum_test_tools.eof.v1 import Container, Section +from ethereum_test_tools.eof.v1.constants import NON_RETURNING_SECTION +from ethereum_test_tools.vm.opcode import Opcodes as Op + +from .. import EOF_FORK_NAME +from .helpers import slot_code_worked, value_code_worked + +REFERENCE_SPEC_GIT_PATH = "EIPS/eip-6206.md" +REFERENCE_SPEC_VERSION = "2f365ea0cd58faa6e26013ea77ce6d538175f7d0" + +pytestmark = pytest.mark.valid_from(EOF_FORK_NAME) + + +@pytest.mark.parametrize( + "target_inputs", + [0, 2, 4], + ids=lambda x: "ti-%d" % x, +) +@pytest.mark.parametrize( + "stack_height", + [0, 2, 4], + ids=lambda x: "h-%d" % x, +) +def test_jumpf_stack_non_returning_rules( + eof_state_test: EOFStateTestFiller, + target_inputs: int, + stack_height: int, +): + """ + Tests for JUMPF validation stack rules. Non-returning section cases. + Valid cases are executed. + """ + container = Container( + name="stack-non-retuning_h-%d_ti-%d" % (stack_height, target_inputs), + sections=[ + Section.Code( + code=Op.JUMPF[1], + code_outputs=NON_RETURNING_SECTION, + ), + Section.Code( + code=Op.PUSH0 * stack_height + Op.JUMPF[2], + code_outputs=NON_RETURNING_SECTION, + max_stack_height=stack_height, + ), + Section.Code( + code=Op.POP * target_inputs + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP, + code_inputs=target_inputs, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=max(2, target_inputs), + ), + ], + ) + + if stack_height < target_inputs: + container.validity_error = EOFException.STACK_UNDERFLOW + + eof_state_test( + data=container, + container_post=Account(storage={slot_code_worked: value_code_worked}), + tx_data=b"\1", + ) + + +@pytest.mark.parametrize( + "source_outputs", + [0, 2, 4], + ids=lambda x: "so-%d" % x, +) +@pytest.mark.parametrize( + "target_outputs", + [0, 2, 4], + ids=lambda x: "to-%d" % x, +) +@pytest.mark.parametrize( + "target_inputs", + [0, 2, 4], + ids=lambda x: "to-%d" % x, +) +@pytest.mark.parametrize("stack_diff", [-1, 0, 1], ids=["less-stack", "same-stack", "more-stack"]) +def test_jumpf_stack_returning_rules( + eof_state_test: EOFStateTestFiller, + source_outputs: int, + target_outputs: int, + target_inputs: int, + stack_diff: int, +): + """ + Tests for JUMPF validation stack rules. Returning section cases. + Valid cases are executed. + """ + if target_outputs > source_outputs: + # These create invalid containers without JUMPF validation, Don't test. + return + if target_inputs == 0 and stack_diff < 0: + # Code generation is impossible for this configuration. Don't test. + return + + target_delta = target_outputs - target_inputs + container = Container( + name="stack-retuning_co-%d_to-%d_ti-%d_diff-%d" + % (source_outputs, target_outputs, target_inputs, stack_diff), + sections=[ + Section.Code( + code=Op.CALLF[1] + Op.SSTORE(slot_code_worked, value_code_worked) + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2 + source_outputs, + ), + Section.Code( + code=Op.PUSH0 * max(0, target_inputs + stack_diff) + Op.JUMPF[2], + code_outputs=source_outputs, + max_stack_height=target_inputs, + ), + Section.Code( + code=(Op.POP * -target_delta if target_delta < 0 else Op.PUSH0 * target_delta) + + Op.RETF, + code_inputs=target_inputs, + code_outputs=target_outputs, + max_stack_height=max(target_inputs, target_outputs), + ), + ], + ) + + if stack_diff < source_outputs - target_outputs: + container.validity_error = EOFException.STACK_UNDERFLOW + elif stack_diff > source_outputs - target_outputs: + container.validity_error = EOFException.STACK_HIGHER_THAN_OUTPUTS + + eof_state_test( + data=container, + container_post=Account(storage={slot_code_worked: value_code_worked}), + tx_data=b"\1", + ) diff --git a/tests/prague/eip7692_eof_v1/eip6206_jumpf/test_jumpf_target.py b/tests/prague/eip7692_eof_v1/eip6206_jumpf/test_jumpf_target.py new file mode 100644 index 0000000000..63db007b05 --- /dev/null +++ b/tests/prague/eip7692_eof_v1/eip6206_jumpf/test_jumpf_target.py @@ -0,0 +1,128 @@ +""" +EOF JUMPF tests covering JUMPF target rules. +""" + +import pytest + +from ethereum_test_tools import Account, EOFException, EOFStateTestFiller +from ethereum_test_tools.eof.v1 import Container, Section +from ethereum_test_tools.eof.v1.constants import NON_RETURNING_SECTION +from ethereum_test_tools.vm.opcode import Opcodes as Op + +from .. import EOF_FORK_NAME +from .helpers import slot_code_worked, value_code_worked + +REFERENCE_SPEC_GIT_PATH = "EIPS/eip-6206.md" +REFERENCE_SPEC_VERSION = "2f365ea0cd58faa6e26013ea77ce6d538175f7d0" + +pytestmark = pytest.mark.valid_from(EOF_FORK_NAME) + + +@pytest.mark.parametrize( + "target_outputs", + [NON_RETURNING_SECTION, 0, 2, 4, 127], + ids=lambda x: "to-%s" % ("N" if x == NON_RETURNING_SECTION else x), +) +@pytest.mark.parametrize( + "source_outputs", + [NON_RETURNING_SECTION, 0, 2, 4, 127], + ids=lambda x: "so-%s" % ("N" if x == NON_RETURNING_SECTION else x), +) +def test_jumpf_target_rules( + eof_state_test: EOFStateTestFiller, + source_outputs: int, + target_outputs: int, +): + """ + Validate the target section rules of JUMPF, and execute valid cases. + We are not testing stack so a lot of the logic is to get correct stack values. + """ + source_non_returning = source_outputs == NON_RETURNING_SECTION + source_height = 0 if source_non_returning else source_outputs + source_section_index = 1 + + target_non_returning = target_outputs == NON_RETURNING_SECTION + target_height = 0 if target_non_returning else target_outputs + target_section_index = 2 + + # Because we are testing the target and not the stack height validation we need to do some work + # to make sure the stack passes validation. + + # `source_extra_push` is how many more pushes we need to match our stack commitments + source_extra_push = max(0, source_height - target_height) + source_section = Section.Code( + code=Op.PUSH0 * (source_height) + + Op.CALLDATALOAD(0) + + Op.RJUMPI[1] + + (Op.STOP if source_non_returning else Op.RETF) + + Op.PUSH0 * source_extra_push + + Op.JUMPF[target_section_index], + code_inputs=0, + code_outputs=source_outputs, + max_stack_height=source_height + max(1, source_extra_push), + ) + + # `delta` is how many stack items the target output is from the input height, and tracks the + # number of pushes or (if negative) pops the target needs to do to match output commitments + delta = 0 if target_non_returning or source_non_returning else target_outputs - source_height + target_section = Section.Code( + code=((Op.PUSH0 * delta) if delta >= 0 else (Op.POP * -delta)) + + Op.CALLF[3] + + (Op.STOP if target_non_returning else Op.RETF), + code_inputs=source_height, + code_outputs=target_outputs, + max_stack_height=max(source_height, source_height + delta), + ) + + base_code = ( + bytes(Op.JUMPF[source_section_index]) + if source_non_returning + else (Op.CALLF[source_section_index](0, 0) + Op.STOP) + ) + base_height = 0 if source_non_returning else 2 + source_outputs + container = Container( + name="so-%s_to-%s" + % ( + "N" if source_non_returning else source_outputs, + "N" if target_non_returning else target_outputs, + ), + sections=[ + Section.Code( + code=base_code, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=base_height, + ), + source_section, + target_section, + Section.Code( + code=Op.SSTORE(slot_code_worked, value_code_worked) + Op.RETF, + code_inputs=0, + code_outputs=0, + max_stack_height=2, + ), + ], + ) + if target_non_returning or source_non_returning: + if not target_non_returning and source_non_returning: + # both as non-returning handled above + container.validity_error = EOFException.INVALID_NON_RETURNING_FLAG + elif source_outputs < target_outputs: + container.validity_error = EOFException.JUMPF_DESTINATION_INCOMPATIBLE_OUTPUTS + + eof_state_test( + data=container, + container_post=Account(storage={slot_code_worked: value_code_worked}), + tx_data=b"\1", + ) + + +@pytest.mark.skip("Not implemented") +def test_jumpf_multi_target_rules( + eof_state_test: EOFStateTestFiller, +): + """ + NOT IMPLEMENTED: + Test a section that contains multiple JUMPF to different targets with different outputs. + """ + pass diff --git a/tests/prague/eip7692_eof_v1/eip663_dupn_swapn_exchange/__init__.py b/tests/prague/eip7692_eof_v1/eip663_dupn_swapn_exchange/__init__.py new file mode 100644 index 0000000000..a25957ce27 --- /dev/null +++ b/tests/prague/eip7692_eof_v1/eip663_dupn_swapn_exchange/__init__.py @@ -0,0 +1,7 @@ +""" +abstract: Tests [EIP-663: SWAPN, DUPN and EXCHANGE instructions](https://eips.ethereum.org/EIPS/eip-663) + Tests for [EIP-663: SWAPN, DUPN and EXCHANGE instructions](https://eips.ethereum.org/EIPS/eip-663). +""" # noqa: E501 + +REFERENCE_SPEC_GIT_PATH = "EIPS/eip-663.md" +REFERENCE_SPEC_VERSION = "b658bb87fe039d29e9475d5cfaebca9b92e0fca2" diff --git a/tests/prague/eip7692_eof_v1/eip663_dupn_swapn_exchange/conftest.py b/tests/prague/eip7692_eof_v1/eip663_dupn_swapn_exchange/conftest.py new file mode 100644 index 0000000000..9c7b2de037 --- /dev/null +++ b/tests/prague/eip7692_eof_v1/eip663_dupn_swapn_exchange/conftest.py @@ -0,0 +1,14 @@ +""" +Pytest fixtures for EIP-663 tests +""" +import pytest + +from ethereum_test_tools import Transaction + + +@pytest.fixture +def tx() -> Transaction: + """ + Produces the default Transaction. + """ + return Transaction(to=0xC0DE, gas_limit=10_000_000) diff --git a/tests/prague/eip7692_eof_v1/eip663_dupn_swapn_exchange/test_dupn.py b/tests/prague/eip7692_eof_v1/eip663_dupn_swapn_exchange/test_dupn.py new file mode 100644 index 0000000000..367ad2fc62 --- /dev/null +++ b/tests/prague/eip7692_eof_v1/eip663_dupn_swapn_exchange/test_dupn.py @@ -0,0 +1,139 @@ +""" +abstract: Tests [EIP-663: SWAPN, DUPN and EXCHANGE instructions](https://eips.ethereum.org/EIPS/eip-663) + Tests for the DUPN instruction. +""" # noqa: E501 + +import pytest + +from ethereum_test_tools import ( + Account, + Environment, + EOFException, + EOFTestFiller, + StateTestFiller, + TestAddress, + Transaction, +) +from ethereum_test_tools.eof.v1 import Container, Section +from ethereum_test_tools.eof.v1.constants import MAX_OPERAND_STACK_HEIGHT, NON_RETURNING_SECTION +from ethereum_test_tools.vm.opcode import Opcodes as Op + +from .. import EOF_FORK_NAME +from . import REFERENCE_SPEC_GIT_PATH, REFERENCE_SPEC_VERSION + +REFERENCE_SPEC_GIT_PATH = REFERENCE_SPEC_GIT_PATH +REFERENCE_SPEC_VERSION = REFERENCE_SPEC_VERSION + + +@pytest.mark.valid_from(EOF_FORK_NAME) +def test_dupn_all_valid_immediates( + tx: Transaction, + state_test: StateTestFiller, +): + """ + Test case for all valid DUPN immediates. + """ + n = 2**8 + values = range(0xD00, 0xD00 + n) + + eof_code = Container( + sections=[ + Section.Code( + code=b"".join(Op.PUSH2(v) for v in values) + + b"".join(Op.SSTORE(x, Op.DUPN[x]) for x in range(0, n)) + + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=n + 2, + ) + ], + ) + + pre = { + TestAddress: Account(balance=1_000_000_000), + tx.to: Account(code=eof_code), + } + + post = {tx.to: Account(storage=dict(zip(range(0, n), reversed(values))))} + + state_test( + env=Environment(), + pre=pre, + post=post, + tx=tx, + ) + + +@pytest.mark.parametrize( + "stack_height,max_stack_height", + [ + [0, 0], + [0, 1], + [1, 1], + [1, 2], + [2**8 - 1, 2**8 - 1], + [2**8 - 1, 2**8], + ], +) +@pytest.mark.valid_from(EOF_FORK_NAME) +def test_dupn_stack_underflow( + stack_height: int, + max_stack_height: int, + eof_test: EOFTestFiller, +): + """ + Test case out of bounds DUPN immediate. + """ + eof_code = Container( + sections=[ + Section.Code( + code=b"".join(Op.PUSH2(v) for v in range(0, stack_height)) + + Op.DUPN[stack_height] + + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=max_stack_height, + ) + ], + ) + eof_test( + data=eof_code, + expect_exception=EOFException.STACK_UNDERFLOW, + ) + + +@pytest.mark.parametrize( + "dupn_operand,max_stack_height,expect_exception", + [ + [0, MAX_OPERAND_STACK_HEIGHT, EOFException.INVALID_MAX_STACK_HEIGHT], + [0, MAX_OPERAND_STACK_HEIGHT + 1, EOFException.MAX_STACK_HEIGHT_ABOVE_LIMIT], + [2**8 - 1, MAX_OPERAND_STACK_HEIGHT, EOFException.INVALID_MAX_STACK_HEIGHT], + [2**8 - 1, MAX_OPERAND_STACK_HEIGHT + 1, EOFException.MAX_STACK_HEIGHT_ABOVE_LIMIT], + ], +) +@pytest.mark.valid_from(EOF_FORK_NAME) +def test_dupn_stack_overflow( + dupn_operand: int, + max_stack_height: int, + expect_exception: EOFException, + eof_test: EOFTestFiller, +): + """ + Test case where DUPN produces an stack overflow. + """ + eof_code = Container( + sections=[ + Section.Code( + code=b"".join(Op.PUSH2(v) for v in range(0, MAX_OPERAND_STACK_HEIGHT)) + + Op.DUPN[dupn_operand] + + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=max_stack_height, + ) + ], + ) + eof_test( + data=eof_code, + expect_exception=expect_exception, + ) diff --git a/tests/prague/eip7692_eof_v1/eip663_dupn_swapn_exchange/test_exchange.py b/tests/prague/eip7692_eof_v1/eip663_dupn_swapn_exchange/test_exchange.py new file mode 100644 index 0000000000..5e1fadd2fb --- /dev/null +++ b/tests/prague/eip7692_eof_v1/eip663_dupn_swapn_exchange/test_exchange.py @@ -0,0 +1,118 @@ +""" +abstract: Tests [EIP-663: SWAPN, DUPN and EXCHANGE instructions](https://eips.ethereum.org/EIPS/eip-663) + Tests for the EXCHANGE instruction. +""" # noqa: E501 + +import pytest + +from ethereum_test_tools import ( + Account, + Environment, + EOFException, + EOFTestFiller, + StateTestFiller, + TestAddress, + Transaction, +) +from ethereum_test_tools.eof.v1 import Container, Section +from ethereum_test_tools.eof.v1.constants import NON_RETURNING_SECTION +from ethereum_test_tools.vm.opcode import Opcodes as Op + +from .. import EOF_FORK_NAME +from . import REFERENCE_SPEC_GIT_PATH, REFERENCE_SPEC_VERSION + +REFERENCE_SPEC_GIT_PATH = REFERENCE_SPEC_GIT_PATH +REFERENCE_SPEC_VERSION = REFERENCE_SPEC_VERSION + + +@pytest.mark.valid_from(EOF_FORK_NAME) +def test_exchange_all_valid_immediates( + tx: Transaction, + state_test: StateTestFiller, +): + """ + Test case for all valid EXCHANGE immediates. + """ + n = 256 + s = 34 + values = range(0x3E8, 0x3E8 + s) + + eof_code = Container( + sections=[ + Section.Code( + code=b"".join(Op.PUSH2(v) for v in values) + + b"".join(Op.EXCHANGE(x) for x in range(0, n)) + + b"".join((Op.PUSH1(x) + Op.SSTORE) for x in range(0, s)) + + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=s + 1, + ) + ], + ) + + pre = { + TestAddress: Account(balance=1_000_000_000), + tx.to: Account(code=eof_code), + } + + # this does the same full-loop exchange + values_rotated = list(range(0x3E8, 0x3E8 + s)) + for e in range(0, n): + a = (e >> 4) + 1 + b = (e & 0x0F) + 1 + a + temp = values_rotated[a] + values_rotated[a] = values_rotated[b] + values_rotated[b] = temp + + post = {tx.to: Account(storage=dict(zip(range(0, s), reversed(values_rotated))))} + + state_test( + env=Environment(), + pre=pre, + post=post, + tx=tx, + ) + + +@pytest.mark.parametrize( + "stack_height,x,y", + [ + # 2 and 3 are the lowest valid values for x and y, which translates to a + # zero immediate value. + pytest.param(0, 2, 3, id="stack_height=0_n=1_m=1"), + pytest.param(1, 2, 3, id="stack_height=1_n=1_m=1"), + pytest.param(2, 2, 3, id="stack_height=2_n=1_m=1"), + pytest.param(17, 2, 18, id="stack_height=17_n=1_m=16"), + pytest.param(17, 17, 18, id="stack_height=17_n=16_m=1"), + pytest.param(32, 17, 33, id="stack_height=32_n=16_m=16"), + ], +) +@pytest.mark.valid_from(EOF_FORK_NAME) +def test_exchange_all_invalid_immediates( + eof_test: EOFTestFiller, + stack_height: int, + x: int, + y: int, +): + """ + Test case for all invalid EXCHANGE immediates. + """ + eof_code = Container( + sections=[ + Section.Code( + code=b"".join(Op.PUSH2(v) for v in range(stack_height)) + + Op.EXCHANGE[x, y] + + Op.POP * stack_height + + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=stack_height, + ) + ], + ) + + eof_test( + data=eof_code, + expect_exception=EOFException.STACK_UNDERFLOW, + ) diff --git a/tests/prague/eip7692_eof_v1/eip663_dupn_swapn_exchange/test_swapn.py b/tests/prague/eip7692_eof_v1/eip663_dupn_swapn_exchange/test_swapn.py new file mode 100644 index 0000000000..2bf4916136 --- /dev/null +++ b/tests/prague/eip7692_eof_v1/eip663_dupn_swapn_exchange/test_swapn.py @@ -0,0 +1,131 @@ +""" +abstract: Tests [EIP-663: SWAPN, DUPN and EXCHANGE instructions](https://eips.ethereum.org/EIPS/eip-663) + Tests for the SWAPN instruction. +""" # noqa: E501 + +import pytest + +from ethereum_test_tools import ( + Account, + Environment, + EOFException, + EOFTestFiller, + StateTestFiller, + TestAddress, + Transaction, +) +from ethereum_test_tools.eof.v1 import Container, Section +from ethereum_test_tools.eof.v1.constants import MAX_OPERAND_STACK_HEIGHT, NON_RETURNING_SECTION +from ethereum_test_tools.vm.opcode import Opcodes as Op + +from .. import EOF_FORK_NAME +from . import REFERENCE_SPEC_GIT_PATH, REFERENCE_SPEC_VERSION + +REFERENCE_SPEC_GIT_PATH = REFERENCE_SPEC_GIT_PATH +REFERENCE_SPEC_VERSION = REFERENCE_SPEC_VERSION + + +@pytest.mark.valid_from(EOF_FORK_NAME) +def test_swapn_all_valid_immediates( + tx: Transaction, + state_test: StateTestFiller, +): + """ + Test case for all valid SWAPN immediates. + """ + n = 256 + values = range(0x500, 0x500 + 257) + + eof_code = Container( + sections=[ + Section.Code( + code=b"".join(Op.PUSH2(v) for v in values) + + b"".join(Op.SSTORE(x, Op.SWAPN[0xFF - x]) for x in range(0, n)) + + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=n + 2, + ) + ], + ) + + pre = { + TestAddress: Account(balance=1_000_000_000), + tx.to: Account(code=eof_code), + } + + values_rotated = list(values[1:]) + [values[0]] + post = {tx.to: Account(storage=dict(zip(range(0, n), reversed(values_rotated))))} + + state_test( + env=Environment(), + pre=pre, + post=post, + tx=tx, + ) + + +@pytest.mark.parametrize( + "swapn_operand", + [ + 0, + 2**8 - 1, + ], +) +@pytest.mark.valid_from(EOF_FORK_NAME) +def test_swapn_on_max_stack( + swapn_operand: int, + eof_test: EOFTestFiller, +): + """ + Test case out of bounds DUPN immediate. + """ + eof_code = Container( + sections=[ + Section.Code( + code=b"".join(Op.PUSH2(v) for v in range(0, MAX_OPERAND_STACK_HEIGHT)) + + Op.SWAPN[swapn_operand] + + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=MAX_OPERAND_STACK_HEIGHT, + ) + ], + ) + eof_test( + data=eof_code, + ) + + +@pytest.mark.parametrize( + "stack_height", + [ + 0, + 1, + 2**8 - 1, + ], +) +@pytest.mark.valid_from(EOF_FORK_NAME) +def test_swapn_stack_underflow( + stack_height: int, + eof_test: EOFTestFiller, +): + """ + Test case out of bounds DUPN immediate. + """ + eof_code = Container( + sections=[ + Section.Code( + code=b"".join(Op.PUSH2(v) for v in range(0, stack_height)) + + Op.SWAPN[stack_height] + + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=MAX_OPERAND_STACK_HEIGHT, + ) + ], + ) + eof_test( + data=eof_code, + expect_exception=EOFException.STACK_UNDERFLOW, + ) diff --git a/tests/prague/eip7480_data_section/__init__.py b/tests/prague/eip7692_eof_v1/eip7480_data_section/__init__.py similarity index 100% rename from tests/prague/eip7480_data_section/__init__.py rename to tests/prague/eip7692_eof_v1/eip7480_data_section/__init__.py diff --git a/tests/prague/eip7692_eof_v1/eip7480_data_section/spec.py b/tests/prague/eip7692_eof_v1/eip7480_data_section/spec.py new file mode 100644 index 0000000000..3b477ba776 --- /dev/null +++ b/tests/prague/eip7692_eof_v1/eip7480_data_section/spec.py @@ -0,0 +1,3 @@ +""" +EOF V1 Constants used throughout all tests +""" diff --git a/tests/prague/eip7480_data_section/test_code_validation.py b/tests/prague/eip7692_eof_v1/eip7480_data_section/test_code_validation.py similarity index 96% rename from tests/prague/eip7480_data_section/test_code_validation.py rename to tests/prague/eip7692_eof_v1/eip7480_data_section/test_code_validation.py index 650e383cee..032815ecf7 100644 --- a/tests/prague/eip7480_data_section/test_code_validation.py +++ b/tests/prague/eip7692_eof_v1/eip7480_data_section/test_code_validation.py @@ -11,7 +11,7 @@ from ethereum_test_tools.eof.v1.constants import NON_RETURNING_SECTION from ethereum_test_tools.vm.opcode import Opcodes as Op -from .spec import EOF_FORK_NAME +from .. import EOF_FORK_NAME REFERENCE_SPEC_GIT_PATH = "EIPS/eip-7480.md" REFERENCE_SPEC_VERSION = "3ee1334ef110420685f1c8ed63e80f9e1766c251" @@ -128,7 +128,7 @@ max_stack_height=1, ), ], - validity_error=EOFException.DEFAULT_EXCEPTION, + validity_error=EOFException.INVALID_DATALOADN_INDEX, ), Container( name="DATALOADN_max_small_data", @@ -141,7 +141,7 @@ ), Section.Data(data="1122334455667788" * 16), ], - validity_error=EOFException.DEFAULT_EXCEPTION, + validity_error=EOFException.INVALID_DATALOADN_INDEX, ), Container( name="DATALOADN_max_half_data", @@ -154,7 +154,7 @@ ), Section.Data(data=("1122334455667788" * 4 * 1024)[2:]), ], - validity_error=EOFException.DEFAULT_EXCEPTION, + validity_error=EOFException.INVALID_DATALOADN_INDEX, ), ] diff --git a/tests/prague/eip7480_data_section/test_data_opcodes.py b/tests/prague/eip7692_eof_v1/eip7480_data_section/test_data_opcodes.py similarity index 99% rename from tests/prague/eip7480_data_section/test_data_opcodes.py rename to tests/prague/eip7692_eof_v1/eip7480_data_section/test_data_opcodes.py index b1cd9b631f..cb15bfab45 100644 --- a/tests/prague/eip7480_data_section/test_data_opcodes.py +++ b/tests/prague/eip7692_eof_v1/eip7480_data_section/test_data_opcodes.py @@ -16,7 +16,7 @@ from ethereum_test_tools.eof.v1.constants import MAX_CODE_SECTIONS, NON_RETURNING_SECTION from ethereum_test_tools.vm.opcode import Opcodes as Op -from .spec import EOF_FORK_NAME +from .. import EOF_FORK_NAME REFERENCE_SPEC_GIT_PATH = "EIPS/eip-7480.md" REFERENCE_SPEC_VERSION = "3ee1334ef110420685f1c8ed63e80f9e1766c251" diff --git a/tests/prague/eip7692_eof_v1/eip7620_eof_create/__init__.py b/tests/prague/eip7692_eof_v1/eip7620_eof_create/__init__.py new file mode 100644 index 0000000000..4655d79f8d --- /dev/null +++ b/tests/prague/eip7692_eof_v1/eip7620_eof_create/__init__.py @@ -0,0 +1,14 @@ +""" +EOFCREATE, RETURNCONTRACT, and container tests + +evmone tests not ported + +create_tx_with_eof_initcode - This calls it invalid, it is now the way to add EOF contacts to state +eofcreate_extcall_returncontract - per the new initcode mode tests you cannot have RETURNCONTRACT + in a deployed contract +eofcreate_dataloadn_referring_to_auxdata - covered by + tests.prague.eip7480_data_section.test_data_opcodes.test_data_section_succeed +eofcreate_initcontainer_return - RETURN is banned in initcode containers +eofcreate_initcontainer_stop - STOP is banned in initcode containers +All TXCREATE tests - TXCREATE has been removed from Prague +""" diff --git a/tests/prague/eip7692_eof_v1/eip7620_eof_create/helpers.py b/tests/prague/eip7692_eof_v1/eip7620_eof_create/helpers.py new file mode 100644 index 0000000000..8e6b638323 --- /dev/null +++ b/tests/prague/eip7692_eof_v1/eip7620_eof_create/helpers.py @@ -0,0 +1,88 @@ +""" +A collection of contracts used in 7620 EOF tests +""" +import itertools + +from ethereum_test_tools import Address +from ethereum_test_tools import Opcodes as Op +from ethereum_test_tools import Transaction +from ethereum_test_tools.eof.v1 import Container, Section +from ethereum_test_tools.eof.v1.constants import NON_RETURNING_SECTION + +"""Storage addresses for common testing fields""" +_slot = itertools.count() +next(_slot) # don't use slot 0 +slot_code_worked = next(_slot) +slot_code_should_fail = next(_slot) +slot_create_address = next(_slot) +slot_calldata = next(_slot) +slot_call_result = next(_slot) +slot_returndata = next(_slot) +slot_returndata_size = next(_slot) + +slot_last_slot = next(_slot) + +value_code_worked = 0x2015 +value_canary_should_not_change = 0x2019 +value_canary_to_be_overwritten = 0x2009 +value_create_failed = 0 +value_call_result_success = 0 + +smallest_runtime_subcontainer = Container( + name="Runtime Subcontainer", + sections=[ + Section.Code( + code=Op.STOP, code_inputs=0, code_outputs=NON_RETURNING_SECTION, max_stack_height=0 + ) + ], +) + +smallest_initcode_subcontainer = Container( + name="Initcode Subcontainer", + sections=[ + Section.Code( + code=Op.RETURNCONTRACT[0](0, 0), + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ), + Section.Container(container=smallest_runtime_subcontainer), + ], +) + + +def fixed_address(index: int) -> Address: + """ + Returns an determinstic address for testing + Parameters + ---------- + index - how foar off of the initial to create the address + + Returns + ------- + An address, unique per index and human friendly for testing + + """ + return Address(0x7E570000 + index) + + +default_address = fixed_address(0) + + +def simple_transaction( + target: Address = default_address, payload: bytes = b"", gas_limit: int = 10_000_000 +): + """ + Creates a simple transaction + Parameters + ---------- + target the target address, defaults to 0x100 + payload the payload, defauls to empty + + Returns + ------- + a transaction instance that can be passed into state_tests + """ + return Transaction( + nonce=1, to=target, gas_limit=gas_limit, gas_price=10, protected=False, data=payload + ) diff --git a/tests/prague/eip7692_eof_v1/eip7620_eof_create/spec.py b/tests/prague/eip7692_eof_v1/eip7620_eof_create/spec.py new file mode 100644 index 0000000000..3b477ba776 --- /dev/null +++ b/tests/prague/eip7692_eof_v1/eip7620_eof_create/spec.py @@ -0,0 +1,3 @@ +""" +EOF V1 Constants used throughout all tests +""" diff --git a/tests/prague/eip7692_eof_v1/eip7620_eof_create/test_eofcreate.py b/tests/prague/eip7692_eof_v1/eip7620_eof_create/test_eofcreate.py new file mode 100644 index 0000000000..405c5d4308 --- /dev/null +++ b/tests/prague/eip7692_eof_v1/eip7620_eof_create/test_eofcreate.py @@ -0,0 +1,510 @@ +""" +Test good and bad EOFCREATE cases +""" + +import pytest + +from ethereum_test_tools import ( + Account, + Environment, + StateTestFiller, + TestAddress, + compute_eofcreate_address, +) +from ethereum_test_tools.eof.v1 import Container, Section +from ethereum_test_tools.eof.v1.constants import NON_RETURNING_SECTION +from ethereum_test_tools.vm.opcode import Opcodes as Op + +from .. import EOF_FORK_NAME +from .helpers import ( + default_address, + fixed_address, + simple_transaction, + slot_call_result, + slot_calldata, + slot_code_worked, + slot_create_address, + slot_last_slot, + slot_returndata_size, + smallest_initcode_subcontainer, + smallest_runtime_subcontainer, + value_call_result_success, + value_canary_to_be_overwritten, + value_code_worked, + value_create_failed, +) + +REFERENCE_SPEC_GIT_PATH = "EIPS/eip-7620.md" +REFERENCE_SPEC_VERSION = "52ddbcdddcf72dd72427c319f2beddeb468e1737" + +pytestmark = pytest.mark.valid_from(EOF_FORK_NAME) + + +def test_simple_eofcreate( + state_test: StateTestFiller, +): + """ + Verifies a simple EOFCREATE case + """ + env = Environment() + pre = { + TestAddress: Account(balance=10**21, nonce=1), + default_address: Account( + code=Container( + sections=[ + Section.Code( + code=Op.SSTORE(0, Op.EOFCREATE[0](0, 0, 0, 0)) + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=4, + ), + Section.Container(container=smallest_initcode_subcontainer), + ], + data=b"abcdef", + ), + storage={0: 0xB17D}, # a canary to be overwritten + ), + } + # Storage in 0 should have the address, + post = { + default_address: Account( + storage={ + 0: compute_eofcreate_address(default_address, 0, smallest_initcode_subcontainer) + } + ) + } + + state_test(env=env, pre=pre, post=post, tx=simple_transaction()) + + +def test_eofcreate_then_call( + state_test: StateTestFiller, +): + """ + Verifies a simple EOFCREATE case, and then calls the deployed contract + """ + env = Environment() + callable_contract = Container( + sections=[ + Section.Code( + code=Op.SSTORE(slot_code_worked, value_code_worked) + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ), + ] + ) + callable_contract_initcode = Container( + sections=[ + Section.Code( + code=Op.RETURNCONTRACT[0](0, 0), + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ), + Section.Container(container=callable_contract), + ] + ) + + callable_address = compute_eofcreate_address(default_address, 0, callable_contract_initcode) + pre = { + TestAddress: Account(balance=10**21, nonce=1), + default_address: Account( + code=Container( + sections=[ + Section.Code( + code=Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, 0)) + + Op.EXTCALL(Op.SLOAD(slot_create_address), 0, 0, 0) + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=4, + ), + Section.Container(container=callable_contract_initcode), + ], + ) + ), + } + # Storage in 0 should have the address, + # + post = { + default_address: Account( + storage={slot_create_address: callable_address, slot_code_worked: value_code_worked} + ), + callable_address: Account(storage={slot_code_worked: value_code_worked}), + } + + state_test(env=env, pre=pre, post=post, tx=simple_transaction()) + + +@pytest.mark.parametrize( + "auxdata_bytes", + [ + pytest.param(b"", id="zero"), + pytest.param(b"aabbcc", id="short"), + pytest.param(b"aabbccddeef", id="one_byte_short"), + pytest.param(b"aabbccddeeff", id="exact"), + pytest.param(b"aabbccddeeffg", id="one_byte_long"), + pytest.param(b"aabbccddeeffgghhii", id="extra"), + ], +) +def test_auxdata_variations(state_test: StateTestFiller, auxdata_bytes: bytes): + """ + Verifies that auxdata bytes are correctly handled in RETURNCONTRACT + """ + env = Environment() + auxdata_size = len(auxdata_bytes) + pre_deploy_header_data_size = 18 + pre_deploy_data = b"AABBCC" + deploy_success = len(auxdata_bytes) + len(pre_deploy_data) >= pre_deploy_header_data_size + + runtime_subcontainer = Container( + name="Runtime Subcontainer with truncated data", + sections=[ + Section.Code( + code=Op.STOP, code_inputs=0, code_outputs=NON_RETURNING_SECTION, max_stack_height=0 + ), + Section.Data(data=pre_deploy_data, custom_size=pre_deploy_header_data_size), + ], + ) + + initcode_subcontainer = Container( + name="Initcode Subcontainer", + sections=[ + Section.Code( + code=Op.MSTORE(0, Op.PUSH32(auxdata_bytes.ljust(32, b"\0"))) + + Op.RETURNCONTRACT[0](0, auxdata_size), + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ), + Section.Container(container=runtime_subcontainer), + ], + ) + + pre = { + TestAddress: Account(balance=10**21, nonce=1), + default_address: Account( + code=Container( + sections=[ + Section.Code( + code=Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, 0)) + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=4, + ), + Section.Container(container=initcode_subcontainer), + ] + ), + storage={slot_create_address: value_canary_to_be_overwritten}, + ), + } + + # Storage in 0 should have the address, + post = { + default_address: Account( + storage={ + slot_create_address: compute_eofcreate_address( + default_address, 0, initcode_subcontainer + ) + if deploy_success + else b"\0" + } + ) + } + + state_test(env=env, pre=pre, post=post, tx=simple_transaction()) + + +def test_calldata(state_test: StateTestFiller): + """ + Verifies CALLDATA passing through EOFCREATE + """ + env = Environment() + + initcode_subcontainer = Container( + name="Initcode Subcontainer", + sections=[ + Section.Code( + code=Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + + Op.SSTORE(slot_calldata, Op.MLOAD(0)) + + Op.RETURNCONTRACT[0](0, Op.CALLDATASIZE), + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=3, + ), + Section.Container(container=smallest_runtime_subcontainer), + ], + ) + + calldata_size = 32 + calldata = b"\x45" * calldata_size + pre = { + TestAddress: Account(balance=10**21, nonce=1), + default_address: Account( + code=Container( + sections=[ + Section.Code( + code=Op.MSTORE(0, Op.PUSH32(calldata)) + + Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, calldata_size)) + + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=4, + ), + Section.Container(container=initcode_subcontainer), + ] + ) + ), + } + + # deployed contract is smallest plus data + deployed_contract = Container( + name="deployed contract", + sections=[ + *smallest_runtime_subcontainer.sections, + Section.Data(data=calldata), + ], + ) + # factory contract Storage in 0 should have the created address, + # created contract storage in 0 should have the calldata + created_address = compute_eofcreate_address(default_address, 0, initcode_subcontainer) + post = { + default_address: Account(storage={slot_create_address: created_address}), + created_address: Account(code=deployed_contract, storage={slot_calldata: calldata}), + } + + state_test(env=env, pre=pre, post=post, tx=simple_transaction()) + + +def test_eofcreate_in_initcode( + state_test: StateTestFiller, +): + """ + Verifies an EOFCREATE occuring within initcode creates that contract + """ + nested_initcode_subcontainer = Container( + sections=[ + Section.Code( + code=Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, 0)) + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.RETURNCONTRACT[1](0, 0), + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=4, + ), + Section.Container(container=smallest_initcode_subcontainer), + Section.Container(container=smallest_runtime_subcontainer), + ] + ) + + env = Environment() + pre = { + TestAddress: Account(balance=10**21, nonce=1), + default_address: Account( + code=Container( + sections=[ + Section.Code( + code=Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, 0)) + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=4, + ), + Section.Container(container=nested_initcode_subcontainer), + ] + ) + ), + } + + outer_address = compute_eofcreate_address(default_address, 0, nested_initcode_subcontainer) + inner_address = compute_eofcreate_address(outer_address, 0, smallest_initcode_subcontainer) + post = { + default_address: Account( + storage={slot_create_address: outer_address, slot_code_worked: value_code_worked} + ), + outer_address: Account( + storage={slot_create_address: inner_address, slot_code_worked: value_code_worked} + ), + } + + state_test(env=env, pre=pre, post=post, tx=simple_transaction()) + + +def test_eofcreate_in_initcode_reverts( + state_test: StateTestFiller, +): + """ + Verifies an EOFCREATE occuring in an initcode is rolled back when the initcode reverts + """ + nested_initcode_subcontainer = Container( + sections=[ + Section.Code( + code=Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, 0)) + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.REVERT(0, 0), + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=4, + ), + Section.Container(container=smallest_initcode_subcontainer), + Section.Container(container=smallest_runtime_subcontainer), + ] + ) + + env = Environment() + pre = { + TestAddress: Account(balance=10**21, nonce=1), + default_address: Account( + code=Container( + sections=[ + Section.Code( + code=Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, 0)) + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=4, + ), + Section.Container(container=nested_initcode_subcontainer), + ] + ), + storage={slot_create_address: value_canary_to_be_overwritten}, + ), + } + + outer_address = compute_eofcreate_address(default_address, 0, nested_initcode_subcontainer) + inner_address = compute_eofcreate_address(outer_address, 0, smallest_initcode_subcontainer) + post = { + default_address: Account( + storage={ + slot_create_address: 0, + slot_code_worked: value_code_worked, + } + ), + outer_address: Account.NONEXISTENT, + inner_address: Account.NONEXISTENT, + } + + state_test(env=env, pre=pre, post=post, tx=simple_transaction()) + + +def test_return_data_cleared( + state_test: StateTestFiller, +): + """ + Verifies the return data is not re-used from a extcall but is cleared upon eofcreate + """ + env = Environment() + callable_address = fixed_address(1) + value_return_canary = 0x4158675309 + value_return_canary_size = 5 + callable_contract = Container( + sections=[ + Section.Code( + code=Op.MSTORE(0, value_return_canary) + Op.RETURN(0, value_return_canary_size), + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ) + ] + ) + + slot_returndata_size_2 = slot_last_slot * 2 + slot_returndata_size + pre = { + TestAddress: Account(balance=10**21, nonce=1), + default_address: Account( + code=Container( + sections=[ + Section.Code( + code=Op.SSTORE(slot_call_result, Op.EXTCALL(callable_address, 0, 0, 0)) + + Op.SSTORE(slot_returndata_size, Op.RETURNDATASIZE) + + Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, 0)) + + Op.SSTORE(slot_returndata_size_2, Op.RETURNDATASIZE) + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=4, + ), + Section.Container(container=smallest_initcode_subcontainer), + ], + ) + ), + callable_address: Account(code=callable_contract, nonce=1), + } + + new_contract_address = compute_eofcreate_address( + default_address, 0, smallest_initcode_subcontainer + ) + post = { + default_address: Account( + storage={ + slot_call_result: value_call_result_success, + slot_returndata_size: value_return_canary_size, + slot_create_address: new_contract_address, + slot_returndata_size_2: 0, + slot_code_worked: value_code_worked, + }, + nonce=1, + ), + callable_address: Account(nonce=1), + new_contract_address: Account(nonce=1), + } + + state_test(env=env, pre=pre, post=post, tx=simple_transaction()) + + +def test_address_collision( + state_test: StateTestFiller, +): + """ + Verifies a simple EOFCREATE case + """ + env = Environment() + + salt_zero_address = compute_eofcreate_address( + default_address, 0, smallest_initcode_subcontainer + ) + salt_one_address = compute_eofcreate_address( + default_address, 1, smallest_initcode_subcontainer + ) + + slot_create_address_2 = slot_last_slot * 2 + slot_create_address + slot_create_address_3 = slot_last_slot * 3 + slot_create_address + pre = { + TestAddress: Account(balance=10**21, nonce=1), + default_address: Account( + code=Container( + sections=[ + Section.Code( + code=Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, 0)) + + Op.SSTORE(slot_create_address_2, Op.EOFCREATE[0](0, 0, 0, 0)) + + Op.SSTORE(slot_create_address_3, Op.EOFCREATE[0](0, 1, 0, 0)) + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=4, + ), + Section.Container(container=smallest_initcode_subcontainer), + ], + ) + ), + salt_one_address: Account(balance=1, nonce=1), + } + post = { + default_address: Account( + storage={ + slot_create_address: salt_zero_address, + slot_create_address_2: value_create_failed, # had an in-transaction collision + slot_create_address_3: value_create_failed, # had a pre-existing collision + slot_code_worked: value_code_worked, + } + ) + } + + # Multiple create fails is expensive, use an absurd amount of gas + state_test(env=env, pre=pre, post=post, tx=simple_transaction(gas_limit=300_000_000_000)) diff --git a/tests/prague/eip7692_eof_v1/eip7620_eof_create/test_eofcreate_failures.py b/tests/prague/eip7692_eof_v1/eip7620_eof_create/test_eofcreate_failures.py new file mode 100644 index 0000000000..be3d2d5979 --- /dev/null +++ b/tests/prague/eip7692_eof_v1/eip7620_eof_create/test_eofcreate_failures.py @@ -0,0 +1,557 @@ +""" +Test good and bad EOFCREATE cases +""" + +import pytest + +from ethereum_test_tools import ( + Account, + Environment, + StateTestFiller, + TestAddress, + compute_eofcreate_address, +) +from ethereum_test_tools.eof.v1 import Container, Section +from ethereum_test_tools.eof.v1.constants import ( + MAX_BYTECODE_SIZE, + MAX_INITCODE_SIZE, + NON_RETURNING_SECTION, +) +from ethereum_test_tools.vm.opcode import Opcodes as Op + +from .. import EOF_FORK_NAME +from .helpers import ( + default_address, + simple_transaction, + slot_code_should_fail, + slot_code_worked, + slot_create_address, + slot_returndata, + slot_returndata_size, + smallest_initcode_subcontainer, + smallest_runtime_subcontainer, + value_canary_should_not_change, + value_code_worked, + value_create_failed, +) + +REFERENCE_SPEC_GIT_PATH = "EIPS/eip-7620.md" +REFERENCE_SPEC_VERSION = "52ddbcdddcf72dd72427c319f2beddeb468e1737" + +pytestmark = pytest.mark.valid_from(EOF_FORK_NAME) + + +@pytest.mark.parametrize( + "revert", + [ + pytest.param(b"", id="empty"), + pytest.param(b"\x08\xc3\x79\xa0", id="Error(string)"), + ], +) +def test_initcode_revert(state_test: StateTestFiller, revert: bytes): + """ + Verifies proper handling of REVERT in initcode + """ + env = Environment() + revert_size = len(revert) + + initcode_subcontainer = Container( + name="Initcode Subcontainer that reverts", + sections=[ + Section.Code( + code=Op.MSTORE(0, Op.PUSH32(revert)) + Op.REVERT(32 - revert_size, revert_size), + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ), + ], + ) + + factory_contract = Container( + name="factory contract", + sections=[ + Section.Code( + code=Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, 0)) + + Op.SSTORE(slot_returndata_size, Op.RETURNDATASIZE) + + Op.RETURNDATACOPY(Op.SUB(32, Op.RETURNDATASIZE), 0, Op.RETURNDATASIZE) + + Op.SSTORE(slot_returndata, Op.MLOAD(0)) + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=4, + ), + Section.Container(container=initcode_subcontainer), + ], + ) + + pre = { + TestAddress: Account(balance=10**21, nonce=1), + default_address: Account(code=factory_contract), + } + + post = { + default_address: Account( + storage={ + slot_create_address: value_create_failed, + slot_returndata_size: revert_size, + slot_returndata: revert, + slot_code_worked: value_code_worked, + } + ) + } + + state_test(env=env, pre=pre, post=post, tx=simple_transaction()) + + +def test_initcode_aborts( + state_test: StateTestFiller, +): + """ + Verifies correct handling of a halt in EOF initcode + """ + env = Environment() + pre = { + TestAddress: Account(balance=10**21, nonce=1), + default_address: Account( + code=Container( + sections=[ + Section.Code( + code=Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, 0)) + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=4, + ), + Section.Container( + container=Container( + sections=[ + Section.Code( + code=Op.INVALID, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=0, + ) + ] + ) + ), + ] + ) + ), + } + # Storage in slot_create_address should not have the address, + post = { + default_address: Account( + storage={ + slot_create_address: value_create_failed, + slot_code_worked: value_code_worked, + } + ) + } + + state_test(env=env, pre=pre, post=post, tx=simple_transaction()) + + +""" +Size of the factory portion of test_eofcreate_deploy_sizes, but as the runtime code is dynamic, we +have to use a pre-calculated size +""" +factory_size = 30 + + +@pytest.mark.parametrize( + "target_deploy_size", + [ + pytest.param(0x4000, id="large"), + pytest.param(MAX_BYTECODE_SIZE, id="max"), + pytest.param(MAX_BYTECODE_SIZE + 1, id="overmax"), + pytest.param(MAX_INITCODE_SIZE - factory_size, id="initcodemax"), + pytest.param(MAX_INITCODE_SIZE - factory_size + 1, id="initcodeovermax"), + pytest.param(0xFFFF - factory_size, id="64k-1"), + ], +) +def test_eofcreate_deploy_sizes( + state_test: StateTestFiller, + target_deploy_size: int, +): + """ + Verifies a mix of runtime contract sizes mixing success and multiple size failure modes. + """ + env = Environment() + + runtime_container = Container( + sections=[ + Section.Code( + code=Op.JUMPDEST * (target_deploy_size - len(smallest_runtime_subcontainer)) + + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=0, + ), + ] + ) + + initcode_subcontainer = Container( + name="Initcode Subcontainer", + sections=[ + Section.Code( + code=Op.RETURNCONTRACT[0](0, 0), + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ), + Section.Container(container=runtime_container), + ], + ) + + assert factory_size == ( + len(initcode_subcontainer) - len(runtime_container) + ), "factory_size is wrong, expected factory_size is %d, calculated is %d" % ( + factory_size, + len(initcode_subcontainer), + ) + + pre = { + TestAddress: Account(balance=10**21, nonce=1), + default_address: Account( + code=Container( + sections=[ + Section.Code( + code=Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, 0)) + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=4, + ), + Section.Container(container=initcode_subcontainer), + ] + ) + ), + } + # Storage in 0 should have the address, + # Storage 1 is a canary of 1 to make sure it tried to execute, which also covers cases of + # data+code being greater than initcode_size_max, which is allowed. + post = { + default_address: Account( + storage={ + slot_create_address: compute_eofcreate_address( + default_address, 0, initcode_subcontainer + ) + if target_deploy_size <= MAX_BYTECODE_SIZE + else value_create_failed, + slot_code_worked: value_code_worked, + } + ) + } + + state_test(env=env, pre=pre, post=post, tx=simple_transaction()) + + +@pytest.mark.parametrize( + "auxdata_size", + [ + pytest.param(MAX_BYTECODE_SIZE - len(smallest_runtime_subcontainer), id="maxcode"), + pytest.param(MAX_BYTECODE_SIZE - len(smallest_runtime_subcontainer) + 1, id="overmaxcode"), + pytest.param(0x10000 - 60, id="almost64k"), + pytest.param(0x10000 - 1, id="64k-1"), + pytest.param(0x10000, id="64k"), + pytest.param(0x10000 + 1, id="over64k"), + ], +) +def test_auxdata_size_failures(state_test: StateTestFiller, auxdata_size: int): + """ + Exercises a number of auxdata size violations, and one maxcode success + """ + env = Environment() + auxdata_bytes = b"a" * auxdata_size + + initcode_subcontainer = Container( + name="Initcode Subcontainer", + sections=[ + Section.Code( + code=Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + + Op.RETURNCONTRACT[0](0, Op.CALLDATASIZE), + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=3, + ), + Section.Container(container=smallest_runtime_subcontainer), + ], + ) + + pre = { + TestAddress: Account(balance=10**21, nonce=1), + default_address: Account( + code=Container( + sections=[ + Section.Code( + code=Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + + Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, Op.CALLDATASIZE)) + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=4, + ), + Section.Container(container=initcode_subcontainer), + ] + ) + ), + } + + deployed_container_size = len(smallest_runtime_subcontainer) + auxdata_size + + # Storage in 0 will have address in first test, 0 in all other cases indicating failure + # Storage 1 in 1 is a canary to see if EOFCREATE opcode halted + post = { + default_address: Account( + storage={ + slot_create_address: compute_eofcreate_address( + default_address, 0, initcode_subcontainer + ) + if deployed_container_size <= MAX_BYTECODE_SIZE + else 0, + slot_code_worked: value_code_worked, + } + ) + } + + state_test(env=env, pre=pre, post=post, tx=simple_transaction(payload=auxdata_bytes)) + + +@pytest.mark.parametrize( + "value", + [ + pytest.param(1, id="1_wei"), + pytest.param(10**9, id="1_gwei"), + ], +) +def test_eofcreate_insufficient_stipend( + state_test: StateTestFiller, + value: int, +): + """ + Exercises an EOFCREATE that fails because the calling account does not have enough ether to + pay the stipend + """ + env = Environment() + initcode_container = Container( + sections=[ + Section.Code( + code=Op.SSTORE(slot_create_address, Op.EOFCREATE[0](value, 0, 0, 0)) + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=4, + ), + Section.Container(container=smallest_initcode_subcontainer), + ] + ) + pre = { + TestAddress: Account(balance=10**11, nonce=1), + default_address: Account(balance=value - 1, code=initcode_container), + } + # create will fail but not trigger a halt, so canary at storage 1 should be set + # also validate target created contract fails + post = { + default_address: Account( + storage={ + slot_create_address: value_create_failed, + slot_code_worked: value_code_worked, + } + ), + compute_eofcreate_address(default_address, 0, initcode_container): Account.NONEXISTENT, + } + + state_test(env=env, pre=pre, post=post, tx=simple_transaction()) + + +def test_insufficient_initcode_gas( + state_test: StateTestFiller, +): + """ + Excercises an EOFCREATE when there is not enough gas for the initcode charge + """ + env = Environment() + + initcode_data = b"a" * 0x5000 + initcode_container = Container( + name="Large Initcode Subcontainer", + sections=[ + Section.Code( + code=Op.RETURNCONTRACT[0](0, 0), + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ), + Section.Container(container=smallest_runtime_subcontainer), + Section.Data(data=initcode_data), + ], + ) + + pre = { + TestAddress: Account(balance=10**21, nonce=1), + default_address: Account( + code=Container( + sections=[ + Section.Code( + code=Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, 0)) + + Op.SSTORE(slot_code_should_fail, value_code_worked) + + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=4, + ), + Section.Container(container=initcode_container), + ], + ), + storage={ + slot_create_address: value_canary_should_not_change, + slot_code_should_fail: value_canary_should_not_change, + }, + ), + } + # enough gas for everything but EVM opcodes and EIP-150 reserves + gas_limit = 21_000 + 32_000 + (len(initcode_data) + 31) // 32 * 6 + # out_of_gas is triggered, so canary won't set value + # also validate target created contract fails + post = { + default_address: Account( + storage={ + slot_create_address: value_canary_should_not_change, + slot_code_should_fail: value_canary_should_not_change, + }, + ), + compute_eofcreate_address(default_address, 0, initcode_container): Account.NONEXISTENT, + } + + state_test(env=env, pre=pre, post=post, tx=simple_transaction(gas_limit=gas_limit)) + + +def test_insufficient_gas_memory_expansion( + state_test: StateTestFiller, +): + """ + Excercises an EOFCREATE when the memory for auxdata has not been expanded but is requested + """ + env = Environment() + + auxdata_size = 0x5000 + initcode_container = Container( + sections=[ + Section.Code( + code=Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, auxdata_size)) + + Op.SSTORE(slot_code_should_fail, slot_code_worked) + + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=4, + ), + Section.Container(container=smallest_initcode_subcontainer), + ], + ) + pre = { + TestAddress: Account(balance=10**21, nonce=1), + default_address: Account( + code=initcode_container, + storage={ + slot_create_address: value_canary_should_not_change, + slot_code_should_fail: value_canary_should_not_change, + }, + ), + } + # enough gas for everything but EVM opcodes and EIP-150 reserves + initcode_container_words = (len(initcode_container) + 31) // 32 + auxdata_size_words = (auxdata_size + 31) // 32 + gas_limit = ( + 21_000 + + 32_000 + + initcode_container_words * 6 + + 3 * auxdata_size_words + + auxdata_size_words * auxdata_size_words // 512 + ) + # out_of_gas is triggered, so canary won't set value + # also validate target created contract fails + post = { + default_address: Account( + storage={ + slot_create_address: value_canary_should_not_change, + slot_code_should_fail: value_canary_should_not_change, + }, + ), + compute_eofcreate_address(default_address, 0, initcode_container): Account.NONEXISTENT, + } + + state_test(env=env, pre=pre, post=post, tx=simple_transaction(gas_limit=gas_limit)) + + +def test_insufficient_returncontract_auxdata_gas( + state_test: StateTestFiller, +): + """ + Excercises an EOFCREATE when there is not enough gas for the initcode charge + """ + env = Environment() + + auxdata_size = 0x5000 + initcode_container = Container( + name="Large Initcode Subcontainer", + sections=[ + Section.Code( + code=Op.RETURNCONTRACT[0](0, auxdata_size), + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ), + Section.Container(container=smallest_runtime_subcontainer), + ], + ) + + pre = { + TestAddress: Account(balance=10**21, nonce=1), + default_address: Account( + code=Container( + sections=[ + Section.Code( + code=Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, 0)) + + Op.SSTORE(slot_code_should_fail, value_code_worked) + + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=4, + ), + Section.Container(container=initcode_container), + ], + ), + storage={ + slot_create_address: value_canary_should_not_change, + slot_code_should_fail: value_canary_should_not_change, + }, + ), + } + # enough gas for everything but EVM opcodes and EIP-150 reserves + initcode_container_words = (len(initcode_container) + 31) // 32 + auxdata_size_words = (auxdata_size + 31) // 32 + gas_limit = ( + 21_000 + + 32_000 + + initcode_container_words * 6 + + 3 * auxdata_size_words + + auxdata_size_words * auxdata_size_words // 512 + ) + # out_of_gas is triggered, so canary won't set value + # also validate target created contract fails + post = { + default_address: Account( + storage={ + slot_create_address: value_canary_should_not_change, + slot_code_should_fail: value_canary_should_not_change, + }, + ), + compute_eofcreate_address(default_address, 0, initcode_container): Account.NONEXISTENT, + } + + state_test(env=env, pre=pre, post=post, tx=simple_transaction(gas_limit=gas_limit)) diff --git a/tests/prague/eip7692_eof_v1/eip7620_eof_create/test_legacy_eof_creates.py b/tests/prague/eip7692_eof_v1/eip7620_eof_create/test_legacy_eof_creates.py new file mode 100644 index 0000000000..1d756a832c --- /dev/null +++ b/tests/prague/eip7692_eof_v1/eip7620_eof_create/test_legacy_eof_creates.py @@ -0,0 +1,130 @@ +""" +Test interactions between CREATE, CREATE2, and EOFCREATE +""" + +from typing import SupportsBytes + +import pytest + +from ethereum_test_tools import Account, Environment +from ethereum_test_tools import Initcode as LegacyInitcode +from ethereum_test_tools import StateTestFiller, TestAddress +from ethereum_test_tools.vm.opcode import Opcodes +from ethereum_test_tools.vm.opcode import Opcodes as Op + +from .. import EOF_FORK_NAME +from .helpers import ( + default_address, + simple_transaction, + slot_code_worked, + slot_create_address, + smallest_initcode_subcontainer, + smallest_runtime_subcontainer, + value_code_worked, + value_create_failed, +) + +REFERENCE_SPEC_GIT_PATH = "EIPS/eip-7620.md" +REFERENCE_SPEC_VERSION = "52ddbcdddcf72dd72427c319f2beddeb468e1737" + +pytestmark = pytest.mark.valid_from(EOF_FORK_NAME) + + +@pytest.mark.parametrize( + "legacy_create_opcode", + [ + pytest.param(Op.CREATE, id="CREATE"), + pytest.param(Op.CREATE2, id="CREATE2"), + ], +) +@pytest.mark.parametrize( + "deploy_code", + [ + pytest.param(smallest_initcode_subcontainer, id="deploy_eof_initcontainer"), + pytest.param(smallest_runtime_subcontainer, id="deploy_eof_container"), + ], +) +def test_cross_version_creates_fail( + state_test: StateTestFiller, + legacy_create_opcode: Opcodes, + deploy_code: SupportsBytes, +): + """ + Verifies that CREATE and CREATE2 cannot create EOF contracts + """ + env = Environment() + salt_param = [0] if legacy_create_opcode == Op.CREATE2 else [] + pre = { + TestAddress: Account(balance=10**21, nonce=1), + default_address: Account( + code=Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + + Op.SSTORE( + slot_create_address, legacy_create_opcode(0, 0, Op.CALLDATASIZE, *salt_param) + ) + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP + ), + } + # Storage in 0 should be empty as the create/create2 should fail, + # and 1 in 1 to show execution continued and did not halt + post = { + default_address: Account( + storage={ + slot_create_address: value_create_failed, + slot_code_worked: value_code_worked, + } + ) + } + + state_test( + env=env, + pre=pre, + post=post, + tx=simple_transaction(payload=bytes(deploy_code)), + ) + + +@pytest.mark.parametrize( + "legacy_create_opcode", + [ + pytest.param(Op.CREATE, id="CREATE"), + pytest.param(Op.CREATE2, id="CREATE2"), + ], +) +@pytest.mark.parametrize( + "deploy_code", + [ + pytest.param(smallest_initcode_subcontainer, id="deploy_eof_initcontainer"), + pytest.param(smallest_runtime_subcontainer, id="deploy_eof_container"), + ], +) +def test_legacy_initcode_eof_contract_fails( + state_test: StateTestFiller, + legacy_create_opcode: Opcodes, + deploy_code: SupportsBytes, +): + """ + Verifies that legacy initcode cannot create EOF + """ + env = Environment() + init_code = LegacyInitcode(deploy_code=deploy_code) + salt_param = [0] if legacy_create_opcode == Op.CREATE2 else [] + factory_code = ( + Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + + Op.SSTORE(slot_create_address, legacy_create_opcode(0, 0, Op.CALLDATASIZE, *salt_param)) + + Op.SSTORE(slot_code_worked, value_code_worked) + ) + + pre = { + TestAddress: Account(balance=10**21, nonce=1), + default_address: Account(code=factory_code), + } + # Storage in 0 should be empty as the final CREATE filed + # and 1 in 1 to show execution continued and did not halt + post = { + default_address: Account( + storage={slot_create_address: value_create_failed, slot_code_worked: value_code_worked} + ) + } + + state_test(env=env, pre=pre, post=post, tx=simple_transaction(payload=bytes(init_code))) diff --git a/tests/prague/eip7692_eof_v1/tracker.md b/tests/prague/eip7692_eof_v1/tracker.md new file mode 100644 index 0000000000..274134d37e --- /dev/null +++ b/tests/prague/eip7692_eof_v1/tracker.md @@ -0,0 +1,136 @@ +# EOF Testing Coverage Tracker + +- [ ] Example Test Case 1 +- [x] Example Test Case 2 (./eip3540_eof_v1/test_example_valid_invalid.py::test_example_valid_invalid) +- [ ] Example Test Case 3 (ethereum/tests: ./src/EOFTestsFiller/validInvalidFiller.yml) + +## EIP-3540: EOF - EVM Object Format v1 + +### Validation + +- [ ] Valid container without data section (ethereum/tests: ./src/EOFTestsFiller/EIP3540/validInvalidFiller.yml) +- [ ] Valid container with data section (ethereum/tests: ./src/EOFTestsFiller/EIP3540/validInvalidFiller.yml) +- [ ] Valid container with truncated data section (ethereum/tests: ./src/EOFTestsFiller/EIP3540/validInvalidFiller.yml) +- [ ] Valid container with data section truncated to empty (ethereum/tests: ./src/EOFTestsFiller/EIP3540/validInvalidFiller.yml) +- [ ] Valid containers with multiple code sections (ethereum/tests: ./src/EOFTestsFiller/validInvalidFiller.yml) +- [ ] Valid containers with max number of code sections +- [ ] Truncated magic (ethereum/tests: ./src/EOFTestsFiller/EIP3540/validInvalidFiller.yml) +- [ ] Valid container except magic (ethereum/tests: ./src/EOFTestsFiller/EIP3540/validInvalidFiller.yml) +- [ ] Truncated before version (ethereum/tests: ./src/EOFTestsFiller/EIP3540/validInvalidFiller.yml) +- [ ] Valid container except version (ethereum/tests: ./src/EOFTestsFiller/EIP3540/validInvalidFiller.yml) +- [ ] Truncated before type section header (ethereum/tests: ./src/EOFTestsFiller/EIP3540/validInvalidFiller.yml) +- [ ] Truncated before type section size (ethereum/tests: ./src/EOFTestsFiller/EIP3540/validInvalidFiller.yml) +- [ ] Truncated type section size (ethereum/tests: ./src/EOFTestsFiller/EIP3540/validInvalidFiller.yml) +- [ ] Truncated before code section header (ethereum/tests: ./src/EOFTestsFiller/validInvalidFiller.yml) +- [ ] Truncated before code section number (ethereum/tests: ./src/EOFTestsFiller/EIP3540/validInvalidFiller.yml) +- [ ] Truncated code section number (ethereum/tests: ./src/EOFTestsFiller/EIP3540/validInvalidFiller.yml) +- [ ] Truncated before code section size (ethereum/tests: ./src/EOFTestsFiller/EIP3540/validInvalidFiller.yml) +- [ ] Truncated code section size (ethereum/tests: ./src/EOFTestsFiller/EIP3540/validInvalidFiller.yml) +- [ ] 0 code section number (ethereum/tests: ./src/EOFTestsFiller/validInvalidFiller.yml) +- [ ] 0 code section size (ethereum/tests: ./src/EOFTestsFiller/EIP3540/validInvalidFiller.yml) +- [ ] 0 code section size with non-empty data section (ethereum/tests: ./src/EOFTestsFiller/EIP3540/validInvalidFiller.yml) +- [ ] No container sections, truncated before data section header (ethereum/tests: ./src/EOFTestsFiller/EIP3540/validInvalidFiller.yml) +- [ ] Container sections present, truncated before data section header (ethereum/tests: ./src/EOFTestsFiller/validInvalidFiller.yml) +- [ ] Truncated before data section size (ethereum/tests: ./src/EOFTestsFiller/EIP3540/validInvalidFiller.yml) +- [ ] Truncated data section size (ethereum/tests: ./src/EOFTestsFiller/EIP3540/validInvalidFiller.yml) +- [ ] Truncated before header terminator (ethereum/tests: ./src/EOFTestsFiller/EIP3540/validInvalidFiller.yml) +- [ ] Truncated before type section (ethereum/tests: ./src/EOFTestsFiller/EIP3540/validInvalidFiller.yml) +- [ ] Type section truncated before outputs (ethereum/tests: ./src/EOFTestsFiller/EIP3540/validInvalidFiller.yml) +- [ ] Type section truncated before max_stack_height (ethereum/tests: ./src/EOFTestsFiller/EIP3540/validInvalidFiller.yml) +- [ ] Type section truncated max_stack_height (ethereum/tests: ./src/EOFTestsFiller/EIP3540/validInvalidFiller.yml) +- [ ] Truncated before code sections (ethereum/tests: ./src/EOFTestsFiller/EIP3540/validInvalidFiller.yml) +- [ ] Truncated code section (ethereum/tests: ./src/EOFTestsFiller/EIP3540/validInvalidFiller.yml) +- [ ] Data section empty, trailing bytes (ethereum/tests: ./src/EOFTestsFiller/EIP3540/validInvalidFiller.yml) +- [ ] Data section non-empty, trailing bytes (ethereum/tests: ./src/EOFTestsFiller/EIP3540/validInvalidFiller.yml) +- [ ] Wrong order of sections (ethereum/tests: ./src/EOFTestsFiller/EIP3540/validInvalidFiller.yml) +- [ ] No data section header (ethereum/tests: ./src/EOFTestsFiller/validInvalidFiller.yml) +- [ ] Multiple data sections (ethereum/tests: ./src/EOFTestsFiller/EIP3540/validInvalidFiller.yml) +- [ ] Unknown section id (ethereum/tests: ./src/EOFTestsFiller/EIP3540/validInvalidFiller.yml) +- [ ] Type section size != 4 * code section number (ethereum/tests: ./src/EOFTestsFiller/validInvalidFiller.yml) +- [ ] Code section with max max_stack_height (ethereum/tests: ./src/EOFTestsFiller/efExample/validInvalidFiller.yml) +- [ ] Code section with max_stack_height above limit (ethereum/tests: ./src/EOFTestsFiller/efExample/validInvalidFiller.yml) +- [ ] Valid code sections with inputs/outputs +- [ ] Valid code section with max inputs +- [ ] Valid code section with max outputs +- [ ] Code sections with invalid number of inputs/outputs (above limit) +- [ ] 0 section with inputs/outputs (ethereum/tests: ./src/EOFTestsFiller/efExample/validInvalidFiller.yml) +- [ ] Multiple type section headers +- [ ] Multiple code section headers +- [ ] Multiple data section headers +- [ ] Container without type section (ethereum/tests: ./src/EOFTestsFiller/efExample/validInvalidFiller.yml) +- [ ] Container without code sections (ethereum/tests: ./src/EOFTestsFiller/EIP3540/validInvalidFiller.yml) +- [ ] Container without data section (ethereum/tests: ./src/EOFTestsFiller/EIP3540/validInvalidFiller.yml) +- [ ] Valid containers without data section and with subcontainers +- [ ] Valid containers with data section and with subcontainers +- [ ] Valid container with maximum number of subcontainers +- [ ] Container with number of subcontainers above the limit +- [ ] Subcontainer section header truncated before subcontainer number +- [ ] Subcontainer section header truncated before subcontainer size +- [ ] Truncated subcontainer size +- [ ] 0 container section numner +- [ ] 0 container size +- [ ] Truncated container section body +- [ ] Multiple container section headers +- [ ] Invalid subcontainer +- [ ] Invalid subcontainer on a deep nesting level + +## EIP-3670: EOF - Code Validation + +### Validation + +- [ ] Code section with invalid opcodes (ethereum/tests: ./src/EOFTestsFiller/efExample/validInvalidFiller.yml) +- [ ] INVALID opcode is valid (ethereum/tests: ./src/EOFTestsFiller/efExample/validInvalidFiller.yml) +- [ ] Truncated PUSH data (ethereum/tests: ./src/EOFTestsFiller/efExample/validInvalidFiller.yml) + +## EIP-4200: EOF - Static relative jumps + +### Validation + +- [ ] Jumps out of section bounds (ethereum/tests: ./src/EOFTestsFiller/efExample/validInvalidFiller.yml) + +## EIP-4750: EOF - Functions + +### Validation + +- [ ] Valid CALLFs (ethereum/tests: ./src/EOFTestsFiller/efExample/validInvalidFiller.yml) +- [ ] CALLFs to non-existing sections (ethereum/tests: ./src/EOFTestsFiller/efExample/validInvalidFiller.yml) + +## EIP-5450: EOF - Stack Validation + +### Validation + +- [ ] Check all terminating opcodes (ethereum/tests: ./src/EOFTestsFiller/efExample/validInvalidFiller.yml) +- [ ] Code section not terminating (executing beyond section end) (ethereum/tests: ./src/EOFTestsFiller/efExample/validInvalidFiller.yml) +- [ ] Stack underflows (ethereum/tests: ./src/EOFTestsFiller/efExample/validInvalidFiller.yml) +- [ ] CALLF stack underflows (ethereum/tests: ./src/EOFTestsFiller/efExample/validInvalidFiller.yml) +- [ ] RETF with extra items on stack (ethereum/tests: ./src/EOFTestsFiller/efExample/validInvalidFiller.yml) +- [ ] Wrong max_stack_height (ethereum/tests: ./src/EOFTestsFiller/efExample/validInvalidFiller.yml) + + +## EIP-6206: EOF - JUMPF and non-returning functions + +### Validation + +- [ ] 0 section returning (ethereum/tests: ./src/EOFTestsFiller/efExample/validInvalidFiller.yml) + +## EIP-7480: EOF - Data section access instructions + +## EIP-663: SWAPN, DUPN and EXCHANGE instructions + +### Validation + +- [ ] A DUPN instruction causes stack overflow +- [ ] A DUPN instruction causes stack underflow +- [ ] A DUPN instruction causes max stack height mismatch +- [ ] A SWAPN instruction causes stack underflow + +### Execution + +- [x] Positive tests for DUPN instructions (./eip663_dupn_swapn_exchange/test_dupn.py::test_dupn_all_valid_immediates) +- [x] Positive tests for SWAPN instructions (./eip663_dupn_swapn_exchange/test_swapn.py::test_swapn_all_valid_immediates) + +## EIP-7069: Revamped CALL instructions + +## EIP-7620: EOF Contract Creation + +## EIP-7698: EOF - Creation transaction diff --git a/tests_consume/test_direct.py b/tests_consume/test_direct.py new file mode 100644 index 0000000000..dbc1f5f2ad --- /dev/null +++ b/tests_consume/test_direct.py @@ -0,0 +1,70 @@ +""" +Executes a JSON test fixture directly against a client using a dedicated +client interface similar to geth's EVM 'blocktest' command. +""" + +import re +from pathlib import Path +from typing import Any, List, Optional + +import pytest + +from ethereum_test_tools.spec.consume.types import TestCaseIndexFile, TestCaseStream +from evm_transition_tool import TransitionTool + +statetest_results: dict[Path, List[dict[str, Any]]] = {} + + +def test_blocktest( # noqa: D103 + test_case: TestCaseIndexFile | TestCaseStream, + evm: TransitionTool, + evm_run_single_test: bool, + fixture_path: Path, + test_dump_dir: Optional[Path], +): + fixture_name = None + if evm_run_single_test: + fixture_name = re.escape(test_case.id) + evm.verify_fixture( + test_case.format, + fixture_path, + fixture_name=fixture_name, + debug_output_path=test_dump_dir, + ) + + +@pytest.fixture(scope="function") +def run_statetest( + test_case: TestCaseIndexFile | TestCaseStream, + evm: TransitionTool, + fixture_path: Path, + test_dump_dir: Optional[Path], +): + """ + Run statetest on the json fixture file if the test result is not already cached. + """ + # TODO: Check if all required results have been tested and delete test result data if so. + # TODO: Can we group the tests appropriately so that this works more efficiently with xdist? + if fixture_path not in statetest_results: + json_result = evm.verify_fixture( + test_case.format, + fixture_path, + fixture_name=None, + debug_output_path=test_dump_dir, + ) + statetest_results[fixture_path] = json_result + + +@pytest.mark.usefixtures("run_statetest") +def test_statetest( # noqa: D103 + test_case: TestCaseIndexFile | TestCaseStream, + fixture_path: Path, +): + test_result = [ + test_result + for test_result in statetest_results[fixture_path] + if test_result["name"] == test_case.id + ] + assert len(test_result) < 2, f"Multiple test results for {test_case.id}" + assert len(test_result) == 1, f"Test result for {test_case.id} missing" + assert test_result[0]["pass"], f"State test failed: {test_result[0]['error']}" diff --git a/tests_consume/test_via_engine_api.py b/tests_consume/test_via_engine_api.py new file mode 100644 index 0000000000..c520000f91 --- /dev/null +++ b/tests_consume/test_via_engine_api.py @@ -0,0 +1,24 @@ +""" +A hive simulator that executes blocks against clients using the +`engine_newPayloadVX` method from the Engine API, verifying +the appropriate VALID/INVALID responses. + +Implemented using the pytest framework as a pytest plugin. +""" + +import pytest + +from ethereum_test_tools.spec.blockchain.types import HiveFixture + + +@pytest.mark.skip(reason="Not implemented yet.") +def test_via_engine_api(fixture: HiveFixture): + """ + 1. Checks that the genesis block hash of the client matches that of the fixture. + 2. Executes the test case fixture blocks against the client under test using the + `engine_newPayloadVX` method from the Engine API, verifying the appropriate + VALID/INVALID responses. + 3. Performs a forkchoice update to finalize the chain and verify the post state. + 4. Checks that the post state of the client matches that of the fixture. + """ + pass diff --git a/tests_consume/test_via_rlp.py b/tests_consume/test_via_rlp.py new file mode 100644 index 0000000000..2f59fc8a46 --- /dev/null +++ b/tests_consume/test_via_rlp.py @@ -0,0 +1,258 @@ +""" +Test a fully instantiated client using RLP-encoded blocks from blockchain tests. + +The test fixtures should have the blockchain test format. The setup sends +the genesis file and RLP-encoded blocks to the client container using hive. +The client consumes these files upon start-up. + +Given a genesis state and a list of RLP-encoded blocks, the test verifies that: +1. The client's genesis block hash matches that defined in the fixture. +2. The client's last block hash matches that defined in the fixture. +""" + +import io +import json +import pprint +import time +from typing import Generator, List, Mapping, Optional, cast + +import pytest +import rich +from hive.client import Client, ClientType +from hive.testing import HiveTest +from pydantic import BaseModel + +from ethereum_test_tools.common.base_types import Bytes +from ethereum_test_tools.common.json import to_json +from ethereum_test_tools.rpc import EthRPC +from ethereum_test_tools.spec.blockchain.types import Fixture, FixtureHeader +from pytest_plugins.consume.hive_ruleset import ruleset + + +class TestCaseTimingData(BaseModel): + """ + The times taken to perform the various steps of a test case (seconds). + """ + + __test__ = False + prepare_files: Optional[float] = None # start of test until client start + start_client: Optional[float] = None + get_genesis: Optional[float] = None + get_last_block: Optional[float] = None + stop_client: Optional[float] = None + total: Optional[float] = None + + @staticmethod + def format_float(num: float | None, precision: int = 4) -> str | None: + """ + Format a float to a specific precision in significant figures. + """ + if num is None: + return None + return f"{num:.{precision}f}" + + def formatted(self, precision: int = 4) -> "TestCaseTimingData": + """ + Return a new instance of the model with formatted float values. + """ + data = {field: self.format_float(value, precision) for field, value in self} + return TestCaseTimingData(**data) + + +@pytest.fixture(scope="function") +def t_test_start() -> float: + """ + The time the test started; used to time fixture+file preparation and total time. + """ + return time.perf_counter() + + +@pytest.fixture(scope="function", autouse=True) +def timing_data(request, t_test_start) -> Generator[TestCaseTimingData, None, None]: + """ + Helper to record timing data for various stages of executing test case. + """ + timing_data = TestCaseTimingData() + yield timing_data + timing_data.total = time.perf_counter() - t_test_start + rich.print(f"\nTimings (seconds): {timing_data.formatted()}") + if hasattr(request.node, "rep_call"): # make available for test reports + request.node.rep_call.timings = timing_data + + +@pytest.fixture(scope="function") +@pytest.mark.usefixtures("timing_data") +def client_genesis(fixture: Fixture) -> dict: + """ + Convert the fixture's genesis block header and pre-state to a client genesis state. + """ + genesis = to_json(fixture.genesis) # NOTE: to_json() excludes None values + alloc = to_json(fixture.pre) + # NOTE: nethermind requires account keys without '0x' prefix + genesis["alloc"] = {k.replace("0x", ""): v for k, v in alloc.items()} + return genesis + + +@pytest.fixture(scope="function") +def blocks_rlp(fixture: Fixture) -> List[Bytes]: + """ + A list of the fixture's blocks encoded as RLP. + """ + return [block.rlp for block in fixture.blocks] + + +@pytest.fixture +def buffered_genesis(client_genesis: dict) -> io.BufferedReader: + """ + Create a buffered reader for the genesis block header of the current test + fixture. + """ + genesis_json = json.dumps(client_genesis) + genesis_bytes = genesis_json.encode("utf-8") + return io.BufferedReader(cast(io.RawIOBase, io.BytesIO(genesis_bytes))) + + +@pytest.fixture +def buffered_blocks_rlp(blocks_rlp: List[bytes], start=1) -> List[io.BufferedReader]: + """ + Convert the RLP-encoded blocks of the current test fixture to buffered readers. + """ + block_rlp_files = [] + for i, block_rlp in enumerate(blocks_rlp): + block_rlp_stream = io.BytesIO(block_rlp) + block_rlp_files.append(io.BufferedReader(cast(io.RawIOBase, block_rlp_stream))) + return block_rlp_files + + +@pytest.fixture +def client_files( + buffered_genesis: io.BufferedReader, + buffered_blocks_rlp: list[io.BufferedReader], +) -> Mapping[str, io.BufferedReader]: + """ + Define the files that hive will start the client with. + + The files are specified as a dictionary whose: + - Keys are the target file paths in the client's docker container, and, + - Values are in-memory buffered file objects. + """ + files = {f"/blocks/{i + 1:04d}.rlp": rlp for i, rlp in enumerate(buffered_blocks_rlp)} + files["/genesis.json"] = buffered_genesis + return files + + +@pytest.fixture +def environment(fixture: Fixture) -> dict: + """ + Define the environment that hive will start the client with using the fork + rules specific for the simulator. + """ + assert fixture.fork in ruleset, f"fork '{fixture.fork}' missing in hive ruleset" + return { + "HIVE_CHAIN_ID": "1", + "HIVE_FORK_DAO_VOTE": "1", + "HIVE_NODETYPE": "full", + **{k: f"{v:d}" for k, v in ruleset[fixture.fork].items()}, + } + + +@pytest.fixture(scope="function") +def client( + hive_test: HiveTest, + client_files: dict, + environment: dict, + client_type: ClientType, + t_test_start: float, + timing_data: TestCaseTimingData, +) -> Generator[Client, None, None]: + """ + Initialize the client with the appropriate files and environment variables. + """ + timing_data.prepare_files = time.perf_counter() - t_test_start + t_start = time.perf_counter() + client = hive_test.start_client( + client_type=client_type, environment=environment, files=client_files + ) + timing_data.start_client = time.perf_counter() - t_start + error_message = ( + f"Unable to connect to the client container ({client_type.name}) via Hive during test " + "setup. Check the client or Hive server logs for more information." + ) + assert client is not None, error_message + yield client + t_start = time.perf_counter() + client.stop() + timing_data.stop_client = time.perf_counter() - t_start + + +@pytest.fixture(scope="function") +def eth_rpc(client: Client) -> EthRPC: + """ + Initialize ethereum RPC client for the execution client under test. + """ + return EthRPC(client_ip=client.ip) + + +def compare_models(expected: FixtureHeader, got: FixtureHeader) -> dict: + """ + Compare two FixtureHeader model instances and return their differences. + """ + differences = {} + for (exp_name, exp_value), (_, got_value) in zip(expected, got): + if exp_value != got_value: + differences[exp_name] = { + "expected ": str(exp_value), + "got (via rpc)": str(got_value), + } + return differences + + +class GenesisBlockMismatchException(Exception): + """ + Used when the client's genesis block hash does not match the fixture. + """ + + def __init__(self, *, expected_header: FixtureHeader, got_header: FixtureHeader): + message = ( + "Genesis block hash mismatch.\n" + f"Expected: {expected_header.block_hash}\n" + f" Got: {got_header.block_hash}." + ) + differences = compare_models(expected_header, got_header) + if differences: + message += ( + "\n\nAdditionally, there are differences between the expected and received " + "genesis block header fields:\n" + f"{pprint.pformat(differences, indent=4)}" + ) + else: + message += ( + "There were no differences in the expected and received genesis block headers." + ) + super().__init__(message) + + +def test_via_rlp( + eth_rpc: EthRPC, + fixture: Fixture, + timing_data: TestCaseTimingData, +): + """ + Verify that the client's state as calculated from the specified genesis state + and blocks matches those defined in the test fixture. + + Test: + + 1. The client's genesis block hash matches `fixture.genesis.block_hash`. + 2. The client's last block's hash matches `fixture.last_block_hash`. + """ + t_start = time.perf_counter() + genesis_block = eth_rpc.get_block_by_number(0) + timing_data.get_genesis = time.perf_counter() - t_start + if genesis_block["hash"] != str(fixture.genesis.block_hash): + raise GenesisBlockMismatchException( + expected_header=fixture.genesis, got_header=FixtureHeader(**genesis_block) + ) + block = eth_rpc.get_block_by_number("latest") + timing_data.get_last_block = time.perf_counter() - timing_data.get_genesis - t_start + assert block["hash"] == str(fixture.last_block_hash), "hash mismatch in last block" diff --git a/tox.ini b/tox.ini index 60b8c6ca48..fe0e6e4723 100644 --- a/tox.ini +++ b/tox.ini @@ -4,8 +4,9 @@ env_list = tests docs -[main] -development_fork = Prague +[forks] +develop = Prague +eip7692 = CancunEIP7692 [testenv] package = wheel @@ -18,7 +19,7 @@ extras = test lint -src = src setup.py +src = src setup.py tests_consume commands = fname8 {[testenv:framework]src} @@ -55,7 +56,7 @@ extras = commands = {[testenv:tests-base]commands} - pytest -n auto + pytest -n auto -k "not slow" [testenv:tests-develop] description = Execute test cases in tests/, including tests for development forks @@ -64,7 +65,16 @@ extras = {[testenv:tests-base]extras} commands = - pytest -n auto --until={[main]development_fork} + pytest -n auto --until={[forks]develop} -k "not slow" + +[testenv:tests-eip7692] +description = Execute test cases in tests/, including tests for EIP-7692 (EOF) + +extras = + {[testenv:tests-base]extras} + +commands = + pytest -n auto --evm-bin=evmone-t8n --fork={[forks]eip7692} -k "not slow" ./tests/prague [testenv:docs] description = Run documentation checks diff --git a/whitelist.txt b/whitelist.txt index c06e368730..9d0ab5ba5b 100644 --- a/whitelist.txt +++ b/whitelist.txt @@ -1,4 +1,6 @@ 0xaa +Account1 +Account2 acl addr address @@ -8,12 +10,14 @@ api apis at5 AutoSection +auxdata balance base64 basefee basename bb besu +bidict big0 big1 blobgasfee @@ -26,6 +30,7 @@ blockhash blocknum blocktest bls +bls12 blueswen boolean br @@ -48,6 +53,7 @@ chainid changelog chfast classdict +cli cli2 codeAddr codecopy @@ -76,6 +82,7 @@ delitem deserialized deserialization Dencun +deprecations dev devnet difficulty @@ -89,14 +96,20 @@ dup dunder EEST eip +eip3540 eips EIPs +eip6110 +eip7002 +el endianness EngineAPI enum env envvar eof +EOF1 +eofparse EOFException esbenp eth @@ -121,7 +134,18 @@ fcu formatOnSave formatter fromhex +frozenbidict func +fp +fp2 +g1 +g1add +g1mul +g1msm +g2 +g2add +g2mul +g2msm gaslimit gasprice GeneralStateTestsFiller @@ -149,12 +173,17 @@ HeaderNonce hexary HexNumber hexsha +hexbytes homebrew html +htmlpath https hyperledger +iat ignoreRevsFile img +imm +immediates incrementing init initcode @@ -167,6 +196,7 @@ ispkg itemName jimporter jq +js json JSON keccak @@ -183,17 +213,20 @@ lllc london macOS mainnet +makereport marioevz markdownlint md metaclass -Misspelled words: +mixhash mkdocs mkdocstrings +msm mypy namespace nav ncheck +nethermind nexternal nGo nJSON @@ -202,16 +235,22 @@ NOP NOPs nPython nSHA +num number ommer ommers opc oprypin origin +ori +P1 +P2 parseable pathlib pdb +perf petersburg +pformat png Pomerantz ppa @@ -219,10 +258,12 @@ ppas pre Pre precompile +predeploy prepend PrevRandao prestateTracer programmatically +pubkey px py pydantic @@ -243,11 +284,14 @@ reentrant repo repo's repos +returndata returndatacopy returndatasize returncontract rlp +rootdir rpc +ruleset runtime sandboxed secp256k1 @@ -296,8 +340,10 @@ todo toml tox Tox +traceback TransactionException trie +triggerable tstorage tx txs @@ -308,6 +354,7 @@ u256 ubuntu ukiyo uncomment +undersize util utils v0 @@ -348,19 +395,26 @@ copytree dedent dest exc +extractall fixturenames fspath funcargs +getfixturevalue getgroup getoption +Golang groupby hookimpl hookwrapper IEXEC IGNORECASE +inifile +isatty iterdir ljust +longreprtext makepyfile +makereport metafunc modifyitems nodeid @@ -374,12 +428,14 @@ params parametrize parametrizer parametrizers +parametrization popen prevrandao pytester pytestmark readline regexes +removesuffix reportinfo ret rglob @@ -389,9 +445,13 @@ runpytest runtest subclasses subcommand +subcontainer substring substrings +tf +teardown testdir +teststatus tmpdir tryfirst trylast @@ -554,6 +614,7 @@ call callcode return delegatecall +eofcreate extcall extdelegatecall staticcall @@ -577,3 +638,6 @@ modexp fi url gz +tT +istanbul +berlin \ No newline at end of file