diff --git a/CLAUDE.md b/CLAUDE.md index 876117cdbd4..068311d4397 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -32,7 +32,6 @@ All commands use `uv run` prefix. ```bash uv sync --all-extras -uv run solc-select use 0.8.24 --always-install uvx pre-commit install ``` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c7b6d87b359..616fd441ecd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -85,7 +85,6 @@ We welcome contributions via pull requests! This section will guide you through uv python install 3.12 uv python pin 3.12 uv sync --all-extras - uv run solc-select use 0.8.24 --always-install ``` See [installation troubleshooting](https://eest.ethereum.org/main/getting_started/installation_troubleshooting) if you encounter issues. diff --git a/README.md b/README.md index edbc50544cc..b339c034c52 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,6 @@ cd execution-spec-tests uv python install 3.11 uv python pin 3.11 uv sync --all-extras -uv run solc-select use 0.8.24 --always-install ``` See [Installation Troubleshooting](https://eest.ethereum.org/main/getting_started/installation_troubleshooting/) in the online docs if you encounter issues. diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index b13770e45d5..f710a04e563 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -10,6 +10,11 @@ Test fixtures for use by clients are available for each release on the [Github r - Python 3.10 support was removed in this release ([#1808](https://github.com/ethereum/execution-spec-tests/pull/1808)). +#### 💥 Important Change for test contributors + +- EEST no longer allows usage of Yul code in Python tests. From now on, please make use of our opcode wrapper. Yul code is now only allowed in the "static tests" located in `./tests/static/` (these are test cases defined by JSON and YAML files instead of Python test functions that were originally maintained in [ethereum/tests](https://github.com/ethereum/tests)). +- In order to fill the static tests (which is not the case by default), please ensure that `solc` is located in your `PATH`. + #### 💥 Important Change for `fill` Users The output behavior of `fill` has changed ([#1608](https://github.com/ethereum/execution-spec-tests/pull/1608)): @@ -27,6 +32,12 @@ Users can select any of the artifacts depending on their testing needs for their ### 🛠️ Framework +#### 🔀 Refactoring + +- 🔀 Move `TransactionType` enum from test file to proper module location in `ethereum_test_types.transaction_types` for better code organization and reusability. +- ✨ Opcode classes now validate keyword arguments and raise `ValueError` with clear error messages. +- 🔀 This PR removes the `solc` requirement to fill Python test cases. Regular test contributors no longer need to concern themselves with `solc` and, as such, the `solc-select` dependency has been removed. The remaining tests that used Yul have been ported to the EEST opcode wrapper mini-lang and the use of Yul in Python tests is no longer supported. Maintainers only: To fill the "static" JSON and YAML tests (`./tests/static/`) locally, `solc` (ideally v0.8.24) must be available in your PATH. + #### `fill` - ✨ Add the `ported_from` test marker to track Python test cases that were converted from static fillers in [ethereum/tests](https://github.com/ethereum/tests) repository ([#1590](https://github.com/ethereum/execution-spec-tests/pull/1590)). diff --git a/docs/dev/porting_legacy_tests.md b/docs/dev/porting_legacy_tests.md index 25909fb40ec..a2513e03971 100644 --- a/docs/dev/porting_legacy_tests.md +++ b/docs/dev/porting_legacy_tests.md @@ -85,16 +85,14 @@ Follow the hyperlinks for lost base coverage (`LBC`) to address coverage gaps. H It's important to note that coverage helps identify missing test paths. If you believe the coverage loss is due to differences in "setup" code between frameworks and doesn't impact the feature you're testing, explain this in your PR. A team member can help with the review. - Also note that yul tests and possibly other tests used `CALLDATALOAD` that might no longer needed when designing a test with python. But we must always investigate if an opcode is not covered anymore to see if its okay. - - For example, review the [discussion in this PR.](https://github.com/ethereum/execution-spec-tests/pull/975#issuecomment-2528792289) + For example, review the [discussion in this PR] to see an example of why and how coverage loss can occur.(https://github.com/ethereum/execution-spec-tests/pull/975#issuecomment-2528792289) ## Resolving Coverage Gaps from Yul Compilation When porting tests from ethereum/tests, you may encounter coverage gaps that are false positives. This commonly occurs because: - **Original tests** often used Yul to define smart contracts, and solc compilation introduces additional opcodes that aren't specifically under test -- **EEST ports** typically use the explicit EEST Opcode mini-language, which is more precise in opcode definition +- **EEST ports** use the explicit EEST Opcode mini-language, which is more precise in opcode definition If coverage analysis shows missing opcodes that were only present due to Yul compilation artifacts (not the actual feature being tested), this can be resolved during PR review by adding the `coverage_missed_reason` parameter: diff --git a/docs/getting_started/installation.md b/docs/getting_started/installation.md index a20d653e903..dbe3bf11e7b 100644 --- a/docs/getting_started/installation.md +++ b/docs/getting_started/installation.md @@ -26,7 +26,7 @@ If installed via `curl`, `uv` will download Python for your target platform if o Clone [execution-spec-tests](https://github.com/ethereum/execution-spec-tests) and install its dependencies. We recommend using Python 3.12, the following uses `uv` to download and configures 3.12 to be the Python version used in execution-spec-tests: -=== "All x86-64 platforms and ARM64 macOS" +=== "All platforms" ```console git clone https://github.com/ethereum/execution-spec-tests @@ -34,19 +34,9 @@ Clone [execution-spec-tests](https://github.com/ethereum/execution-spec-tests) a uv python install 3.12 uv python pin 3.12 uv sync --all-extras - uv run solc-select use 0.8.24 --always-install ``` -=== "ARM64 Linux" - - ```console - git clone https://github.com/ethereum/execution-spec-tests - uv python install 3.12 - uv python pin 3.12 - cd execution-spec-tests - uv sync --all-extras - ``` - Then follow [this guide](./installation_troubleshooting.md#problem-exception-failed-to-compile-yul-source) to build the `solc` binary from source and copy it to the expected location. +Static tests/maintainers only: To learn how to build the `solc` binary from source (optional) follow [this guide](./installation_troubleshooting.md#problem-exception-failed-to-compile-yul-source). ## Installation Troubleshooting diff --git a/docs/getting_started/installation_troubleshooting.md b/docs/getting_started/installation_troubleshooting.md index d4d70ac1e4a..12cdb775ded 100644 --- a/docs/getting_started/installation_troubleshooting.md +++ b/docs/getting_started/installation_troubleshooting.md @@ -38,11 +38,11 @@ This page provides guidance on how to troubleshoot common issues that may arise ### Problem: `CERTIFICATE_VERIFY_FAILED` -!!! danger "Problem: `Failed to install solc ... CERTIFICATE_VERIFY_FAILED`" - When running either `uv run solc-select use 0.8.24 --always-install` or `fill` you encounter the following error: +!!! danger "Problem: `Failed to ... CERTIFICATE_VERIFY_FAILED`" + When running `fill` you might encounter the following error: ```bash - Exit: Failed to install solc version 0.8.24: + Exit: Failed to ...: ``` === "Ubuntu" @@ -66,15 +66,8 @@ This page provides guidance on how to troubleshoot common issues that may arise ### Problem: `Exception: failed to compile yul source` -!!! danger "Problem: `Running fill fails with tests that contain yul source code` on ARM platforms" - If you're using `x86_64`, try to manually run `solc-select` to install the required version of the `solc` binary: - - ```shell - uv run solc-select use 0.8.24 --always-install - ``` - - Otherwise, this can happen when you're using an ARM64 OS but followed the x86-64 installation guide. - To resolve the issue you must build solidity from source (avoid 0.8.24 as it might require building z3 from source as well). +!!! danger "Problem: `Running fill on static_tests fails with tests that contain yul source code` on ARM platforms" + To resolve the issue you must acquire the `solc` binary e.g. by building solidity from source. The guide below installs v0.8.28 but you might prefer to choose a different version. !!! success "Solution: Build solc from source" The following steps have been tested on Ubuntu 24.04.2 LTS ARM64: @@ -88,7 +81,7 @@ This page provides guidance on how to troubleshoot common issues that may arise cp ./solc/solc $HOME/Documents/execution-spec-tests/.venv/bin/ chmod +x $HOME/Documents/execution-spec-tests/.venv/bin/solc ``` - Running `uv run solc --version` should now return the expected version. Verify that `fill` works by running `uv run fill -m yul_test` in the execution-spec-tests root folder. + Running `uv run solc --version` should now return the expected version. ## Problem: `ValueError: unsupported hash type ripemd160` diff --git a/docs/running_tests/test_formats/blockchain_test_engine_x.md b/docs/running_tests/test_formats/blockchain_test_engine_x.md index daa9004662a..a6b5831ad10 100644 --- a/docs/running_tests/test_formats/blockchain_test_engine_x.md +++ b/docs/running_tests/test_formats/blockchain_test_engine_x.md @@ -14,7 +14,7 @@ The key optimization is that **clients need only be started once per group** ins Instead of including large pre-allocation state in each test fixture, this format references a pre-allocation groups folder (`pre_alloc`) which contains all different pre-allocation combinations organized by group. -A single JSON fixture file is composed of a JSON object where each key-value pair is a different [`BlockchainTestEngineXFixture`](#BlockchainTestEngineXFixture) test object, with the key string representing the test name. +A single JSON fixture file is composed of a JSON object where each key-value pair is a different [`BlockchainTestEngineXFixture`](#blockchaintestenginexfixture) test object, with the key string representing the test name. The JSON file path plus the test name are used as the unique test identifier. @@ -48,7 +48,7 @@ Each file in the `pre_alloc` folder corresponds to a pre-allocation group identi ## Consumption -For each [`BlockchainTestEngineXFixture`](#BlockchainTestEngineXFixture) test object in the JSON fixture file, perform the following steps: +For each [`BlockchainTestEngineXFixture`](#blockchaintestenginexfixture) test object in the JSON fixture file, perform the following steps: 1. **Load Pre-Allocation Group**: - Read the appropriate file from the `pre_alloc` folder in the same directory diff --git a/docs/running_tests/test_formats/common_types.md b/docs/running_tests/test_formats/common_types.md index 2cf3b1a0d43..04085a48bc4 100644 --- a/docs/running_tests/test_formats/common_types.md +++ b/docs/running_tests/test_formats/common_types.md @@ -86,6 +86,10 @@ State allocation represented as a JSON object, where the keys are the addresses Maps forks to blob schedule configurations as defined by [EIP-7840](https://eips.ethereum.org/EIPS/eip-7840). +### `Environment` + +Contains blockchain-related configuration that provides the context in which a test is run. + ### `ForkBlobSchedule` A fork blob schedule as defined by [EIP-7840](https://eips.ethereum.org/EIPS/eip-7840) as a JSON dictionary with the following entries: diff --git a/docs/writing_tests/fork_methods.md b/docs/writing_tests/fork_methods.md index 0c7b5eb9727..44e75693712 100644 --- a/docs/writing_tests/fork_methods.md +++ b/docs/writing_tests/fork_methods.md @@ -151,8 +151,6 @@ Methods for fork identification and comparison: ```python fork.name() # Returns the name of the fork fork.transition_tool_name(block_number=0, timestamp=0) # Returns name for transition tools -fork.solc_name() # Returns name for the solc compiler -fork.solc_min_version() # Returns minimum solc version supporting this fork fork.is_deployed() # Returns whether the fork is deployed to mainnet ``` diff --git a/docs/writing_tests/tutorials/blockchain.md b/docs/writing_tests/tutorials/blockchain.md index 6310f5d631a..d6082529d5c 100644 --- a/docs/writing_tests/tutorials/blockchain.md +++ b/docs/writing_tests/tutorials/blockchain.md @@ -9,7 +9,6 @@ Before proceeding with this tutorial, it is assumed that you have prior knowledg - Repository set-up, see [installation](../../getting_started/installation.md).and run an execution specification test as outlined in the . - Able to run `fill`, see [Getting Started: Filling Tests](../../filling_tests/getting_started.md). - Understand how to read a [blockchain test](https://ethereum-tests.readthedocs.io/en/latest/test_filler/blockchain_filler.html). -- Know the basics of [Yul](https://docs.soliditylang.org/en/latest/yul.html), which is an EVM assembly language. - Familiarity with [Python](https://docs.python.org/3/tutorial/). - Understand how to write an execution spec [state transition test](./state_transition.md). @@ -26,15 +25,7 @@ A smart contract is defined that is called by each transaction in the test. It s ```python contract_addr: Account( balance=1000000000000000000000, - code=Yul( - """ - { - let next_slot := sload(0) - sstore(next_slot, number()) - sstore(0, add(next_slot, 1)) - } - """ - ), + code=code, storage={ 0x00: 0x01, }, diff --git a/docs/writing_tests/tutorials/state_transition.md b/docs/writing_tests/tutorials/state_transition.md index 8db7b5079d4..3377a9e0983 100644 --- a/docs/writing_tests/tutorials/state_transition.md +++ b/docs/writing_tests/tutorials/state_transition.md @@ -1,177 +1,123 @@ # State Transition Tests -This tutorial teaches you to create a state transition execution specification test. These tests verify that a starting pre-state will reach a specified post-state after executing a single transaction. +This tutorial teaches you to create a state transition execution specification test using the Python Opcodes minilang for writing EVM bytecode. These tests verify that a starting pre-state will reach a specified post-state after executing a single transaction. In this example, we'll create a simple contract using bytecode and then interact with it through a transaction to verify the expected state changes. + +For an overview of different test types available, see [Types of Tests](../../writing_tests/types_of_tests.md). ## Pre-requisites -Before proceeding with this tutorial, it is assumed that you have prior knowledge and experience with the following: +This tutorial will require some prior knowledge and experience with the following: -- Repository set-up, see [installation](../../getting_started/installation.md).and run an execution specification test as outlined in the . -- Able to run `fill`, see [Getting Started: Filling Tests](../../filling_tests/getting_started.md). -- Understand how to read a [static state transition test](https://ethereum-tests.readthedocs.io/en/latest/state-transition-tutorial.html#the-source-code). -- Know the basics of [Yul](https://docs.soliditylang.org/en/latest/yul.html), which is an EVM assembly language. -- Familiarity with [Python](https://docs.python.org/3/tutorial/). +- Repository set-up, see [installation](../../getting_started/installation.md). +- Ability to run `fill`, see [Getting Started: Filling Tests](../../filling_tests/getting_started.md). +- Basic familiarity with [Python](https://docs.python.org/3/tutorial/). -## Example Tests +## Building a State Test -The most effective method of learning how to write tests is to study a couple of straightforward examples. In this tutorial we will go over the [Yul](https://github.com/ethereum/execution-spec-tests/blob/main/tests/homestead/yul/test_yul_example.py#L19) state test. +The most effective method of learning how to write tests is to study a straightforward example. In this tutorial we will build a simple state test that deploys a contract with bytecode and verifies its execution. -### Yul Test +### Complete Test Example -You can find the source code for the Yul test in [tests/homestead/yul/test_yul_example.py](../../tests/homestead/yul/test_yul_example/index.md). -It is the spec test equivalent of this [static test](https://github.com/ethereum/tests/blob/develop/src/GeneralStateTestsFiller/stExample/yulExampleFiller.yml). +We'll examine a simple test that uses the Python Opcodes minilang to write EVM bytecode. This example is based on the CHAINID opcode test from `tests/istanbul/eip1344_chainid/test_chainid.py`. -Lets examine each section. +Let's examine each section. ```python -""" -Test Yul Source Code Examples -""" +"""State test tutorial demonstrating contract deployment and interaction.""" ``` In Python, multi-line strings are denoted using `"""`. As a convention, a file's purpose is often described in the opening string of the file. ```python -from ethereum_test_forks import Fork, Frontier, Homestead -from ethereum_test_tools import ( - Account, - Alloc, - Environment, - StateTestFiller, - Transaction, - YulCompiler, -) +import pytest + +from ethereum_test_tools import Account, Alloc, Environment, StateTestFiller, Transaction +from ethereum_test_tools.vm.opcode import Opcodes as Op ``` -In this snippet the required constants, types and helper functions are imported from `ethereum_test_tools` and `ethereum_test_forks`. We will go over these as we come across them. +In this snippet the required constants, types and helper functions are imported from `ethereum_test_tools`. The `Opcodes` class (aliased as `Op`) provides the Python minilang for writing EVM bytecode. We will go over these as we come across them. ```python -@pytest.mark.valid_from("Homestead") +@pytest.mark.valid_from("Istanbul") ``` In Python this kind of definition is called a [*decorator*](https://docs.python.org/3/search.html?q=decorator). It modifies the action of the function after it. -In this case, the decorator is a custom [pytest fixture](https://docs.pytest.org/en/latest/explanation/fixtures.html) defined by the execution-specs-test framework that specifies that the test is valid for the [Homestead fork](https://ethereum.org/en/history/#homestead) and all forks after it. The framework will then fill this test case for all forks in the fork range specified by the command-line arguments. +In this case, the decorator is a custom [pytest mark](https://docs.pytest.org/en/latest/how-to/mark.html) defined by the execution-specs-test framework that specifies that the test is valid for the [Istanbul fork](https://ethereum.org/en/history/#istanbul) and all forks after it. The framework will then fill this test case for all forks in the fork range specified by the command-line arguments. + +For more information about test markers and fork validity, see [Test Markers](../../writing_tests/test_markers.md). !!! info "Filling the test" To fill this test for all the specified forks, we can specify pytest's `-k` flag that [filters test cases by keyword expression](https://docs.pytest.org/en/latest/how-to/usage.html#specifying-tests-selecting-tests): ```console - fill -k test_yul + fill -k test_state_test_example ``` and to fill it for a specific fork range, we can provide the `--from` and `--until` command-line arguments: ```console - fill -k test_yul --from London --until Paris + fill -k test_state_test_example --from London --until Paris ``` ```python -def test_yul(state_test: StateTestFiller, pre: Alloc, yul: YulCompiler, fork: Fork): - """ - Test YUL compiled bytecode. - """ +def test_state_test_example(state_test: StateTestFiller, pre: Alloc): + """Test state transition using Opcodes minilang bytecode.""" ``` This is the format of a [Python function](https://docs.python.org/3/tutorial/controlflow.html#defining-functions). It starts with `def ():`, and then has indented code for the function. The function definition ends when there is a line that is no longer indented. As with files, by convention functions start with a string that explains what the function does. -!!! note "The `state_test` function argument" - This test defines a state test and, as such, *must* include the `state_test` in its function arguments. This is a callable object (actually a wrapper class to the `StateTest`); we will see how it is called later. +The function parameters (`state_test` and `pre`) are [pytest fixtures](https://docs.pytest.org/en/latest/explanation/fixtures.html) provided by the execution-spec-tests framework. Pytest fixtures are a powerful dependency injection mechanism that automatically provide objects to your test functions. -!!! note "The `pre` function argument" - For all types of tests, it is highly encouraged that we define the `pre` allocation as a function argument, which will be populated with the pre-state requirements during the execution of the test function (see below). +**The `state_test` fixture** is a callable that you *must* include in *state test* function arguments. When called at the end of your test function with the environment, pre-state, transaction, and expected post-state, it generates the actual test fixtures. This callable is a wrapper around the `StateTest` class. + +**The `pre` fixture** provides an `Alloc` object that manages the pre-state allocation for your test. It offers methods like `fund_eoa()` and `deploy_contract()` that automatically generate unique addresses and add accounts to the blockchain state that will exist before your transaction executes. The fixture handles address generation and ensures no conflicts occur. ```python - env = Environment() + env = Environment(number=1) ``` -This line specifies that `env` is an [`Environment`](https://github.com/ethereum/execution-spec-tests/blob/8b4504aaf6ae0b69c3e847a6c051e64fcefa4db0/src/ethereum_test_tools/common/types.py#L711) object, and that we just use the default parameters. -If necessary we can modify the environment to have different block gas limits, block numbers, etc. -In most tests the defaults are good enough. - -For more information, [see the static test documentation](../../running_tests/test_formats/state_test.md). +This line specifies that `env` is an [`Environment`][ethereum_test_types.Environment] object. In this example, we only override the block `number` to 1, leaving all other values at their defaults. It's recommended to use default values whenever possible and only specify custom values when required for your specific test scenario. (For all available fields, see the pydantic model fields in the source code of [`Environment`][ethereum_test_types.Environment] and [`EnvironmentGeneric`](https://github.com/ethereum/execution-spec-tests/blob/b4d7826bec631574a6fb95d0c58d2c8c4d6e02ca/src/ethereum_test_types/block_types.py#L76) from which `Environment` inherits.) #### Pre State -For every test we need to define the pre-state requirements, so we are certain of what is on the "blockchain" before the transaction is executed. -It can be used as a [dictionary](https://docs.python.org/3/tutorial/datastructures.html#dictionaries), which is the Python term for an associative array, but the appropriate way to populate it is by using the methods `fund_eoa`, `deploy_contract` or `fund_address` from the `Alloc` object. +For every test we need to define the pre-state requirements, so we are certain of what is on the "blockchain" before the transaction is executed. The `pre` fixture provides an `Alloc` object with methods to create accounts that are automatically added to the pre-state. In this example we are using the `deploy_contract` method to deploy a contract to some address available in the pre-state. ```python contract_address = pre.deploy_contract( - code=yul( - """ - { - function f(a, b) -> c { - c := add(a, b) - } - - sstore(0, f(1, 2)) - return(0, 32) - } - """ - ), - balance=0x0BA1A9CE0BA1A9CE, + code=Op.PUSH1(0x03) + Op.PUSH1(0x00) + Op.SSTORE + Op.STOP ) ``` -Specifically we deploy a contract with yul code that adds two numbers and stores the result in storage. - -```python - balance=0x0BA1A9CE0BA1A9CE, -``` - -This field is the balance: the amount of Wei that the account has. It usually doesn't matter what its value is in the case of state test contracts. +Specifically we deploy a contract written with Opcodes minilang code that stores the value `0x03` at storage slot `0x00`. The code consists of: -```python - contract_address = pre.deploy_contract( -``` +- `PUSH1(0x03)`: Push the value 3 onto the stack. +- `PUSH1(0x00)`: Push the storage key 0 onto the stack. +- `SSTORE`: Store the value at the specified key. +- `STOP`: End execution. -As return value of the `deploy_contract` method we get the address where the contract was deployed and put it in the `contract_address` variable, which will later be used in the transaction. +As the return value of the `deploy_contract` method, we get the address where the contract was deployed. This address is stored in the `contract_address` variable, which will later be used as the target of our transaction. -```python - storage={ - 0x00: 0x00, - }, -``` - -We could also specify a starting storage for the contract, which is done by adding a `storage` parameter to the `deploy_contract` method. +You can also specify additional parameters for the contract if needed: -```python - code=yul( -``` +- `balance` parameter to set the contract's initial balance (though often not necessary for state test contracts) +- `storage` parameter to set initial storage values (though in this example we don't need initial storage since our contract will set it through the `SSTORE` opcode) -Here we define the [Yul](https://docs.soliditylang.org/en/v0.8.17/yul.html) code for the contract. It is defined as a multi-line string and starts and ends with curly braces (`{ }`). +You can combine opcodes using the `+` operator to create more complex bytecode sequences. -When running the test filler `fill`, the solidity compiler `solc` will automatically translate the Yul to EVM opcode at runtime. +Generally for execution spec tests the `SSTORE` instruction acts as a high-level assertion method to check pre to post-state changes. The test filler achieves this by verifying that the correct value is held within post-state storage, hence we can validate that the bytecode has run successfully. -!!! note - Currently Yul and direct EVM opcode are supported in execution spec tests. +Next, we need to create an account that will send the transaction to our contract: ```python - """ - { - function f(a, b) -> c { - c := add(a, b) - } - sstore(0, f(1, 2)) - return(0, 32) - } - """ + sender = pre.fund_eoa() ``` -Within this example test Yul code we have a function definition, and inside it we are using the Yul `add` instruction. When compiled with `solc` it translates the instruction directly to the `ADD` opcode. For further Yul instructions [see here](https://docs.soliditylang.org/en/latest/yul.html#evm-dialect). Notice that function is utilized with the Yul `sstore` instruction, which stores the result of `add(1, 2)` to the storage address `0x00`. - -Generally for execution spec tests the `sstore` instruction acts as a high-level assertion method to check pre to post-state changes. The test filler achieves this by verifying that the correct value is held within post-state storage, hence we can validate that the Yul code has run successfully. - -```python - sender = pre.fund_eoa(amount=0x0BA1A9CE0BA1A9CE) -``` - -In this line we specify that we require a single externally owned account (EOA) with a balance of `0x0BA1A9CE0BA1A9CE` Wei. +This line creates a single externally owned account (EOA) with a default balance. You can specify a custom amount with `amount=0x0BA1A9CE0BA1A9CE` if needed. The returned object, which includes a private key, an address, and a nonce, is stored in the `sender` variable and will later be used as the sender of the transaction. @@ -179,38 +125,27 @@ The returned object, which includes a private key, an address, and a nonce, is s ```python tx = Transaction( - ty=0x0, - chain_id=0x01, + ty=0x2, sender=sender, to=contract_address, - gas_limit=500000, - gas_price=10, - protected=False if fork in [Frontier, Homestead] else True, + gas_limit=100_000, ) ``` -With the pre-state built, we can add a description for the [`Transaction`](https://github.com/ethereum/execution-spec-tests/blob/8b4504aaf6ae0b69c3e847a6c051e64fcefa4db0/src/ethereum_test_tools/common/types.py#L887). +With the pre-state built, we can now create the transaction that will call our contract. Let's examine the key components of this [`Transaction`][ethereum_test_types.Transaction] (for all available fields, see the source code of [`Transaction`][ethereum_test_types.Transaction] and [`TransactionGeneric`](https://github.com/ethereum/execution-spec-tests/blob/b4d7826bec631574a6fb95d0c58d2c8c4d6e02ca/src/ethereum_test_types/transaction_types.py#L163) from which `Transaction` inherits). -```python - sender=sender, -``` +- **`sender=sender`**: We use the EOA we created earlier, which already has the necessary information to sign the transaction and contains the correct `nonce`. The `nonce` is a protection mechanism to prevent replay attacks - it must equal the number of transactions sent from the sender's address, starting from zero. The framework automatically manages nonce incrementing for us. -We use the sender variable from the pre-state to specify the sender of the transaction, which already has the necessary information to sign the transaction, and also contains the correct `nonce` for the transaction. +- **`to=contract_address`**: This specifies the address of the contract we want to call, which is the contract we deployed earlier. -The `nonce` is a protection mechanism to prevent replay attacks, and the current rules of Ethereum require that the nonce of a transaction is equal to the number of transactions sent from the sender's address, starting from zero. This means that the first transaction sent from an address must have a nonce of zero, the second transaction must have a nonce of one, and so on. +- **`gas_limit=100_000`**: This sets a high enough gas limit to ensure our simple contract execution doesn't run out of gas. -The `nonce` field of the `sender` variable is automatically incremented for us by the `Transaction` object when the transaction is signed, so if we were to create another transaction with the same sender, the nonce would be incremented by one yet another time. - -```python - to=contract_address, -``` - -The `to` field specifies the address of the contract we want to call and, in this case, it is the address of the contract we deployed earlier. - -For more information, [see the static test documentation](../../running_tests/test_formats/state_test.md) +- **`ty=0x2`**: This specifies the transaction type (EIP-1559). #### Post State +Now we need to define what we expect the blockchain state to look like after our transaction executes: + ```python post = { contract_address: Account( @@ -223,16 +158,25 @@ For more information, [see the static test documentation](../../running_tests/te This is the post-state which is equivalent to [`expect`](https://ethereum-tests.readthedocs.io/en/latest/test_filler/state_filler.html#expect) in static tests, but without the indexes. It is similar to the pre-state, except that we do not need to specify everything, only those accounts and fields we wish to test. -In this case, we look at the storage of the contract we called and add to it what we expect to see. In this example storage cell `0x00` should be `0x03` as in the pre-state we essentially stored the result of the Yul instruction `add(1, 2)`. +In this case, we look at the storage of the contract we called and add to it what we expect to see. In this example storage cell `0x00` should be `0x03` as we stored this value using the `SSTORE` opcode in our contract bytecode. -#### State Test +#### Running the State Test + +Finally, we execute the test by calling the state test wrapper with all our defined components: ```python state_test(env=env, pre=pre, post=post, tx=tx) ``` -This line calls the wrapper to the `StateTest` object that provides all the objects required (for example, the fork parameter) in order to fill the test, generate the test fixtures and write them to file (by default, `./fixtures/_tests/example/yul_example/test_yul.json`). +This line calls the wrapper to the `StateTest` object that provides all the objects required in order to fill the test, generate the test fixtures and write them to file (by default, `./fixtures/_tests/example/state_test_example/test_state_test_example.json`). + +Note that even though we defined a `StateTest`, the `fill` command will also generate other derivative test fixtures: `BlockchainTest`, `BlockchainTestEngine`, and `BlockchainTestEngineX`. For more information about test types and when to use each, see [Test Types: Prefer StateTest for Single Transactions](../types_of_tests.md#prefer-state_test-for-single-transactions). ## Conclusion -At this point you should be able to state transition tests within a single block. +At this point you should be able to write state transition tests within a single block. + +## Next Steps + +- Learn about [Adding a New Test](../../writing_tests/adding_a_new_test.md) to understand test organization and structure. +- Explore [Fork Methods](../../writing_tests/fork_methods.md) for writing tests that adapt to different Ethereum forks. diff --git a/docs/writing_tests/tutorials/state_transition_bad_opcode.md b/docs/writing_tests/tutorials/state_transition_bad_opcode.md deleted file mode 100644 index fbb2030498e..00000000000 --- a/docs/writing_tests/tutorials/state_transition_bad_opcode.md +++ /dev/null @@ -1,279 +0,0 @@ -# Bad Opcode Test - -TODO: This content is out-of-date (it's not included in the docs) - -The source code for this test was TODO here tests/vm/test_opcode_tests.py. -We will only go over the parts that are new. - -We use [Python string templates](https://docs.python.org/3/library/string.html#template-strings), so we need to import that library. - -```python -from string import Template -``` - -In this test we need a couple of addresses, so we create them here. -Python lets us specify `*` when we need a string repeated multiple times, which makes for more readable code than `0x00...000C0DE`. - -```python - code_addr = "0x" + "0"*(40-4) + "C0DE" - goat_addr = "0x" + "0"*(40-4) + "60A7" -``` - -We create `env` and `tx` first because they are constant. -This function will `yield` multiple tests, but always with the same `env` and `tx` values. - -```python - env = Environment() - - tx = Transaction( - . - . - . - ) -``` - -Here we create two post states. -We will use whichever one is appropriate to the test we create. - -```python - post_valid = { - code_addr: Account( - storage={0x00: 1}, - ), - } - - post_invalid = { - code_addr: Account( - storage={0x00: 0}, - ), - } -``` - -Here we define a function (`opc_valid`) inside another function. -Python supports this, and it has two advantages: - -- Avoid namespace pollution by restricting the function to where it is needed. -- Functions defined inside other functions can use the parameters and local variables of those functions. - In this case, we need to use `fork`. - -```python - # Check if an Opcode is valid - def opc_valid(opc): - """ - Return whether opc will be evaluated as valid by the test or not. - Note that some opcodes are evaluated as invalid because of the way they act - """ -``` - -This is the syntax for Python comments, `# `. - -```python - # PUSH0 is only valid Shanghai and later -``` - -Opcode 0x5F (`PUSH0`) is only valid starting with the Shanghai fork. -We don't know what will be the fork names after Shanghai, so it is easiest to specify that prior to Shanghai it is invalid. -We don't need to worry about forks prior to London because the decorator for this test says it is only valid from London. - -```python - if fork in {"london", "paris"} and opc==0x5F: -``` - -Python has a [set data structure](https://docs.python.org/3/tutorial/datastructures.html#sets). -We use this structure when the order of the values are irrelevant, and we just want to be able to check if something is a member or not. - -Note that [`if` statements](https://docs.python.org/3/tutorial/controlflow.html#if-statements) are also followed by a colon (`:`) and the code inside them indented. -That is the general Python syntax. - -```python - return False -``` - -Boolean values in Python are either `True` or `False`. - -This test works by running an opcode and then does a [`SSTORE`](https://www.evm.codes/#55?fork=merge). -Opcodes that terminate execution, such as [`STOP`](https://www.evm.codes/#00?fork=merge) and [`RETURN`](https://www.evm.codes/#f3?fork=merge) also cause the `SSTORE` not to happen, so they must be treated as invalid. -The same is true for [`JUMP`](https://www.evm.codes/#56?fork=merge). - -```python - # Valid opcodes, but they are terminal, and so cause - # the SSTORE not to happen - if opc in {0x00, 0xF3, 0xFD, 0xFF}: - return False - - - # Jumps. If you jump to a random location, you skip the SSTORE - if opc in {0x56}: - return False -``` - -Next we return `True` for supported opcodes. - -```python - # Opcodes that aren't part of a range - # 0x20 - SHA3 - # 0xFA - STATICCALL - if opc in {0x20, 0xFA}: - return True - -``` - -In Python, as in math, you can use `a < b < c` for `a < b and b < c`. - -```python - # Arithmetic opcodes - if 0x01 <= opc <= 0x0b: - return True - - . - . - . -``` - -The last part the function returns `False`. -If we got here, then this is not a valid opcode. - -```python - return False - # End of opc_valid -``` - -As this is the end of the function, the next code line is no longer indented (compared to the function definition code). - -This is a [`for` loop](https://docs.python.org/3/tutorial/controlflow.html#for-statements). -For loops iterate over a sequence, and the [`range`](https://docs.python.org/3/tutorial/controlflow.html#the-range-function) function, in this case, gives us the range 0..255. -As with functions and `if` statements, the `for` loop has a colon and includes the indented code. - -```python - # For every possible opcode - for opc in range(256): -``` - -We have two post states. -One, `post_valid`, has the value of `1` in storage location `0`. -The other, `post_invalid` has the value of `0` in storage location `0`. -But `SELFDESTRUCT` destroys the contract so there is no longer an account at that address. -Neither is valid, so we just skip that test case. - -```python - # We can't check SELFDESTRUCT using this technique - if opc in {0xFF}: - continue -``` - -We need the opcode in hexadecimal. -The function [`hex`](https://docs.python.org/3/library/functions.html#hex) gives us the hexadecimal number in hex. -However, it also gives us a `0x` prefix, which we don't want, so we use a [slice](https://www.w3schools.com/python/gloss_python_string_slice.asp) to remove the first two characters. - -```python - opc_hex = hex(opc)[2:] -``` - -We need `opc_hex` to be two characters. -If the length is only one, prepend a zero. - -```python - if len(opc_hex) == 1: - opc_hex = "0" + opc_hex -``` - -This is a [`Template` string](https://docs.python.org/3/library/string.html#template-strings). -This means we'll be able to substitute template variables (`${}`) with values to produce the actual code. - -```python - yul_code = Template(""" - { -``` - -We start with a call `0x00...0060A7` (a.k.a. `goat_addr`) so we'll have some return data. -Otherwise, [`RETURNDATACOPY`](https://www.evm.codes/#3e?fork=merge) will fail and appear like it is not an opcode. - -```python - pop(call(gas(), 0x60A7, 0, 0, 0, 0, 0)) - - // fails on opcodes with >20 inputs - // (currently dup16, at 17 inputs, is the - // one that goes deepest) - // - // Follow with 32 NOPs (0x5B) to handle PUSH, which has an immediate - // operand -``` - -Opcodes can have two types of operands: - -- Immediate operands, which are part of the bytecode. - For example, `6001` is [`PUSH1`](https://www.evm.codes/#60?fork=merge) with the value `0x01`. -- Implied operands (a.k.a. stack operands), which come from the stack. - -This [`verbatim`](https://docs.soliditylang.org/en/v0.8.17/yul.html#verbatim) code provides both operand types. -The code, `${opcode}${nop32}` is the opcode we are testing, followed by 32 copies of 0x5B. -When `0x5B` is not used as an operand, it is [`JUMPDEST`](https://www.evm.codes/#5b?fork=merge) and does nothing. - -```python - verbatim_20i_0o(hex"${opcode}${nop32}", -``` - -The opcode string is followed by the input parameters (in this case, twenty of them). -These can be Yul expressions, but for the sake of simplicity here we just use constant values. - -```python - 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF) -``` - -If the opcode terminates the smart contract execution (as invalid opcodes do), we don't get here. -If we do get here, write to storage cell `0x00` to record that fact. - -Note the syntax `let := `. This is how you specify variables in Yul. - -```python - // We only get here is the opcode is legit (and it doesn't terminate - // execution like STOP and RETURN) - let zero := 0 - let one := 1 - sstore(zero, one) - } -``` - -Replace `${opcode}` with the one byte hex code, and `${nop32}` with 32 copies of `5b` (for NOP). - -```python - """).substitute(opcode=opc_hex, nop32="5B"*32) - pre = { - TestAddress: Account(balance=0x0BA1A9CE0BA1A9CE), - codeAddr: Account( - balance=0, - nonce=1, - code=Yul(yul_code) - ), -``` - -This is the account for `0x00..0060A7`. -It just returns data (all zeros). - -```python - goat_addr: Account( - balance=0, - nonce=1, - code=Yul("{ return(0, 0x100) }"), - ) - } -``` - -Every time the `for` loop gets here, it [`yields`](https://docs.python.org/3/reference/expressions.html#yieldexpr) a separate test. -Over the entire for loop, it yields 255 different tests. - -```python - yield StateTest( - env=env, - pre=pre, - tx=tx, - post=(post_valid if opc_valid(opc) else post_invalid), - ) -``` - -The Python format for the [ternary operation](https://en.wikipedia.org/wiki/Ternary_conditional_operator) is a bit different from C-like languages. -In C like languages the syntax is ` ? : `. -In Python it is ` if else `. diff --git a/docs/writing_tests/writing_a_new_test.md b/docs/writing_tests/writing_a_new_test.md index 2ebd83a7699..2c5281a33b9 100644 --- a/docs/writing_tests/writing_a_new_test.md +++ b/docs/writing_tests/writing_a_new_test.md @@ -156,10 +156,6 @@ The code can be in either of the following formats: - `str`, representing an hexadecimal format of the opcodes. - `Code` compilable object. -Currently supported built-in compilable objects are: - -- `Yul` object containing [Yul source code](https://docs.soliditylang.org/en/latest/yul.html). - `Code` objects can be concatenated together by using the `+` operator. ## Verifying the Accounts' Post States @@ -201,9 +197,6 @@ It can verify the following properties of an account: - `code`: Bytecode contained by the account. To verify that an account contains no code, this property needs to be set to "0x" or "". - - It is not recommended to verify Yul compiled code in the output account, - because the bytecode can change from version to version. - `storage`: Storage within the account represented as a `dict` object. All storage keys that are expected to be set must be specified, and if a diff --git a/pyproject.toml b/pyproject.toml index 18c15c7dc23..be18aab8960 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,6 @@ dependencies = [ "semver>=3.0.1,<4", "pydantic>=2.10.0,<3", "rich>=13.7.0,<14", - "solc-select>=1.0.4,<2", "filelock>=3.15.1,<4", "ethereum-types>=0.2.1,<0.3", "pyyaml>=6.0.2,<7", diff --git a/pytest-check-eip-versions.ini b/pytest-check-eip-versions.ini index dc3c3e52ac9..b8263df6022 100644 --- a/pytest-check-eip-versions.ini +++ b/pytest-check-eip-versions.ini @@ -11,7 +11,6 @@ addopts = -p pytest_plugins.spec_version_checker.spec_version_checker -p pytest_plugins.concurrency -p pytest_plugins.filler.pre_alloc - -p pytest_plugins.solc.solc -p pytest_plugins.filler.filler -p pytest_plugins.shared.execute_fill -p pytest_plugins.forks.forks diff --git a/pytest-execute-hive.ini b/pytest-execute-hive.ini index cbb20237947..ef05601d13a 100644 --- a/pytest-execute-hive.ini +++ b/pytest-execute-hive.ini @@ -11,7 +11,6 @@ addopts = -p pytest_plugins.concurrency -p pytest_plugins.execute.sender -p pytest_plugins.execute.pre_alloc - -p pytest_plugins.solc.solc -p pytest_plugins.execute.rpc.hive -p pytest_plugins.execute.execute -p pytest_plugins.shared.execute_fill diff --git a/pytest-execute.ini b/pytest-execute.ini index 527959bd872..3ec70273ca3 100644 --- a/pytest-execute.ini +++ b/pytest-execute.ini @@ -12,7 +12,6 @@ addopts = -p pytest_plugins.concurrency -p pytest_plugins.execute.sender -p pytest_plugins.execute.pre_alloc - -p pytest_plugins.solc.solc -p pytest_plugins.execute.execute -p pytest_plugins.shared.execute_fill -p pytest_plugins.execute.rpc.remote_seed_sender diff --git a/pytest.ini b/pytest.ini index 95aab37d542..ee6147a5c15 100644 --- a/pytest.ini +++ b/pytest.ini @@ -11,10 +11,9 @@ markers = addopts = -p pytest_plugins.concurrency -p pytest_plugins.filler.pre_alloc - -p pytest_plugins.solc.solc -p pytest_plugins.filler.filler - -p pytest_plugins.filler.static_filler -p pytest_plugins.filler.ported_tests + -p pytest_plugins.filler.static_filler -p pytest_plugins.shared.execute_fill -p pytest_plugins.forks.forks -p pytest_plugins.eels_resolver diff --git a/src/ethereum_test_forks/__init__.py b/src/ethereum_test_forks/__init__.py index 650b689b6bf..9ed70aa5fe5 100644 --- a/src/ethereum_test_forks/__init__.py +++ b/src/ethereum_test_forks/__init__.py @@ -35,15 +35,13 @@ TransitionFork, forks_from, forks_from_until, - get_closest_fork_with_solc_support, + get_closest_fork, get_deployed_forks, get_development_forks, get_fork_by_name, get_forks, get_forks_with_no_descendants, get_forks_with_no_parents, - get_forks_with_solc_support, - get_forks_without_solc_support, get_from_until_fork_set, get_last_descendants, get_relative_fork_markers, @@ -85,7 +83,7 @@ "get_transition_forks", "forks_from", "forks_from_until", - "get_closest_fork_with_solc_support", + "get_closest_fork", "get_deployed_forks", "get_development_forks", "get_transition_fork_predecessor", @@ -93,8 +91,6 @@ "get_fork_by_name", "get_forks_with_no_descendants", "get_forks_with_no_parents", - "get_forks_with_solc_support", - "get_forks_without_solc_support", "get_relative_fork_markers", "get_forks", "get_from_until_fork_set", diff --git a/src/ethereum_test_forks/base_fork.py b/src/ethereum_test_forks/base_fork.py index 4feceb7c306..9f8b04f2dfe 100644 --- a/src/ethereum_test_forks/base_fork.py +++ b/src/ethereum_test_forks/base_fork.py @@ -16,8 +16,6 @@ Union, ) -from semver import Version - from ethereum_test_base_types import AccessList, Address, BlobSchedule from ethereum_test_base_types.conversions import BytesConvertible from ethereum_test_vm import EVMCodeType, Opcodes @@ -576,12 +574,6 @@ def solc_name(cls) -> str: """Return fork name as it's meant to be passed to the solc compiler.""" pass - @classmethod - @abstractmethod - def solc_min_version(cls) -> Version: - """Return minimum version of solc that supports this fork.""" - pass - @classmethod def is_deployed(cls) -> bool: """ diff --git a/src/ethereum_test_forks/forks/forks.py b/src/ethereum_test_forks/forks/forks.py index d8f068a22b5..ff5d9f509db 100644 --- a/src/ethereum_test_forks/forks/forks.py +++ b/src/ethereum_test_forks/forks/forks.py @@ -6,8 +6,6 @@ from pathlib import Path from typing import List, Literal, Mapping, Optional, Sized, Tuple -from semver import Version - from ethereum_test_base_types import AccessList, Address, BlobSchedule, Bytes, ForkBlobSchedule from ethereum_test_base_types.conversions import BytesConvertible from ethereum_test_vm import EVMCodeType, Opcodes @@ -46,11 +44,6 @@ def solc_name(cls) -> str: return cls._solc_name return cls.name().lower() - @classmethod - def solc_min_version(cls) -> Version: - """Return minimum version of solc that supports this fork.""" - return Version.parse("0.8.20") - @classmethod def header_base_fee_required(cls, block_number: int = 0, timestamp: int = 0) -> bool: """At genesis, header must not contain base fee.""" @@ -929,11 +922,6 @@ def get_blob_constant(cls, name: str) -> int | Literal["big"]: ) return retrieved_constant - @classmethod - def solc_min_version(cls) -> Version: - """Return minimum version of solc that supports this fork.""" - return Version.parse("0.8.24") - @classmethod def header_excess_blob_gas_required(cls, block_number: int = 0, timestamp: int = 0) -> bool: """Excess blob gas is required starting from Cancun.""" @@ -1131,11 +1119,6 @@ def is_deployed(cls) -> bool: """ return False - @classmethod - def solc_min_version(cls) -> Version: - """Return 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[Address]: """ @@ -1421,11 +1404,6 @@ def valid_opcodes( Opcodes.CLZ, ] + super(Prague, cls).valid_opcodes() - @classmethod - def solc_min_version(cls) -> Version: - """Return 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[Address]: """ @@ -1514,8 +1492,3 @@ def is_deployed(cls) -> bool: development. """ return False - - @classmethod - def solc_min_version(cls) -> Version: - """Return 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/helpers.py b/src/ethereum_test_forks/helpers.py index e3434efdec7..601590d4515 100644 --- a/src/ethereum_test_forks/helpers.py +++ b/src/ethereum_test_forks/helpers.py @@ -77,30 +77,11 @@ def get_parent_fork(fork: Type[BaseFork]) -> Type[BaseFork]: return parent_fork -def get_forks_with_solc_support(solc_version: Version) -> List[Type[BaseFork]]: - """Return list of all fork classes that are supported by solc.""" - return [fork for fork in get_forks() if solc_version >= fork.solc_min_version()] - - -def get_forks_without_solc_support(solc_version: Version) -> List[Type[BaseFork]]: - """Return list of all fork classes that aren't supported by solc.""" - return [fork for fork in get_forks() if solc_version < fork.solc_min_version()] - - -def get_closest_fork_with_solc_support( - fork: Type[BaseFork], solc_version: Version -) -> Optional[Type[BaseFork]]: - """ - Return closest fork, potentially the provided fork itself, that has - solc support. - """ +def get_closest_fork(fork: Type[BaseFork], solc_version: Version) -> Optional[Type[BaseFork]]: + """Return None if BaseFork is passed, otherwise return the fork itself.""" if fork is BaseFork: return None - return ( - fork - if solc_version >= fork.solc_min_version() - else get_closest_fork_with_solc_support(get_parent_fork(fork), solc_version) - ) + return fork def get_transition_forks() -> Set[Type[BaseFork]]: diff --git a/src/ethereum_test_forks/tests/test_forks.py b/src/ethereum_test_forks/tests/test_forks.py index 6ee5533ab25..deb6a1b8a9f 100644 --- a/src/ethereum_test_forks/tests/test_forks.py +++ b/src/ethereum_test_forks/tests/test_forks.py @@ -4,7 +4,6 @@ import pytest from pydantic import BaseModel -from semver import Version from ethereum_test_base_types import BlobSchedule @@ -31,10 +30,8 @@ Fork, forks_from, forks_from_until, - get_closest_fork_with_solc_support, get_deployed_forks, get_forks, - get_forks_with_solc_support, transition_fork_from_to, transition_fork_to, ) @@ -298,18 +295,6 @@ def test_tx_types(): # noqa: D103 Cancun.tx_types() == list(range(4)) # noqa: B015 -def test_solc_versioning(): # noqa: D103 - assert len(get_forks_with_solc_support(Version.parse("0.8.20"))) == 13 - assert len(get_forks_with_solc_support(Version.parse("0.8.24"))) > 13 - - -def test_closest_fork_supported_by_solc(): # noqa: D103 - assert get_closest_fork_with_solc_support(Paris, Version.parse("0.8.20")) == Paris - assert get_closest_fork_with_solc_support(Cancun, Version.parse("0.8.20")) == Shanghai - assert get_closest_fork_with_solc_support(Cancun, Version.parse("0.8.24")) == Cancun - assert get_closest_fork_with_solc_support(Prague, Version.parse("0.8.24")) == Cancun - - @pytest.mark.parametrize( "fork", [ diff --git a/src/ethereum_test_specs/static_state/common/compile_yul.py b/src/ethereum_test_specs/static_state/common/compile_yul.py index 6620565e51d..6e548d46b26 100644 --- a/src/ethereum_test_specs/static_state/common/compile_yul.py +++ b/src/ethereum_test_specs/static_state/common/compile_yul.py @@ -13,8 +13,6 @@ def safe_solc_command( source_path = Path(source_file) if not source_path.exists(): raise FileNotFoundError(f"Source file not found: {source_file}") - if source_path.suffix not in (".yul", ".sol"): - raise ValueError(f"Invalid file extension for solc: {source_path.suffix}") cmd: list[str] = ["solc"] diff --git a/src/ethereum_test_tools/__init__.py b/src/ethereum_test_tools/__init__.py index 87eacfe92d2..1617bf76ef5 100644 --- a/src/ethereum_test_tools/__init__.py +++ b/src/ethereum_test_tools/__init__.py @@ -82,8 +82,6 @@ Initcode, Switch, While, - Yul, - YulCompiler, ) from .utility.generators import ( DeploymentTestType, @@ -157,8 +155,6 @@ "While", "Withdrawal", "WithdrawalRequest", - "Yul", - "YulCompiler", "add_kzg_version", "call_return_code", "ceiling_division", diff --git a/src/ethereum_test_tools/tests/conftest.py b/src/ethereum_test_tools/tests/conftest.py deleted file mode 100644 index 9dec259a8c4..00000000000 --- a/src/ethereum_test_tools/tests/conftest.py +++ /dev/null @@ -1,19 +0,0 @@ -"""Common pytest fixtures for ethereum_test_tools tests.""" - -import pytest -from semver import Version - -from ethereum_test_forks import Frontier - -from ..code import Solc - -SOLC_PADDING_VERSION = Version.parse("0.8.21") - - -@pytest.fixture(scope="session") -def solc_version() -> Version: - """Return the version of solc being used for tests.""" - solc_version = Solc("").version.finalize_version() - if solc_version < Frontier.solc_min_version(): - raise Exception("Unsupported solc version: {}".format(solc_version)) - return solc_version diff --git a/src/ethereum_test_tools/tests/test_code.py b/src/ethereum_test_tools/tests/test_code.py index fc79ccd0f0b..48465367dd1 100644 --- a/src/ethereum_test_tools/tests/test_code.py +++ b/src/ethereum_test_tools/tests/test_code.py @@ -1,29 +1,28 @@ """Test suite for `ethereum_test.code` module.""" from string import Template -from typing import Mapping, SupportsBytes +from typing import Mapping import pytest from semver import Version from ethereum_clis import TransitionTool -from ethereum_test_base_types import Account, Address, Bytes, Hash, TestAddress, TestPrivateKey +from ethereum_test_base_types import Account, Address, Hash, TestAddress, TestPrivateKey from ethereum_test_fixtures import BlockchainFixture from ethereum_test_forks import ( Cancun, Fork, Homestead, Shanghai, - get_closest_fork_with_solc_support, get_deployed_forks, ) from ethereum_test_specs import StateTest from ethereum_test_types import Alloc, Environment, Transaction from ethereum_test_vm import Opcodes as Op from ethereum_test_vm import UndefinedOpcodes +from pytest_plugins.solc.solc import SOLC_EXPECTED_MIN_VERSION -from ..code import CalldataCase, Case, Conditional, Initcode, Solc, Switch, Yul -from .conftest import SOLC_PADDING_VERSION +from ..code import CalldataCase, Case, Conditional, Initcode, Switch @pytest.fixture(params=get_deployed_forks()) @@ -32,33 +31,12 @@ def fork(request: pytest.FixtureRequest): return request.param -@pytest.fixture() -def yul_code( - request: pytest.FixtureRequest, - fork: Fork, - padding_before: str | None, - padding_after: str | None, -) -> bytes: - """Return the Yul code for the test.""" - yul_code_snippets = request.param - compiled_yul_code = b"" - if padding_before is not None: - compiled_yul_code += Bytes(padding_before) - for yul_code in yul_code_snippets: - compiled_yul_code += bytes( - Yul(yul_code, fork=get_closest_fork_with_solc_support(fork, Solc().version)) - ) - if padding_after is not None: - compiled_yul_code += Bytes(padding_after) - return compiled_yul_code - - @pytest.fixture() def expected_bytes(request: pytest.FixtureRequest, solc_version: Version, fork: Fork): """Return the expected bytes for the test.""" expected_bytes = request.param if isinstance(expected_bytes, Template): - if solc_version < SOLC_PADDING_VERSION or fork <= Homestead: + if solc_version < SOLC_EXPECTED_MIN_VERSION or fork <= Homestead: solc_padding = "" else: solc_padding = "00" @@ -66,7 +44,7 @@ def expected_bytes(request: pytest.FixtureRequest, solc_version: Version, fork: if isinstance(expected_bytes, bytes): if fork >= Shanghai: expected_bytes = b"\x5f" + expected_bytes[2:] - if solc_version < SOLC_PADDING_VERSION or fork <= Homestead: + if solc_version < SOLC_EXPECTED_MIN_VERSION or fork <= Homestead: return expected_bytes else: return expected_bytes + b"\x00" @@ -74,83 +52,6 @@ def expected_bytes(request: pytest.FixtureRequest, solc_version: Version, fork: raise Exception("Unsupported expected_bytes type: {}".format(type(expected_bytes))) -@pytest.mark.parametrize( - ["yul_code", "padding_before", "padding_after", "expected_bytes"], - [ - pytest.param( - ( - """ - { - sstore(1, 2) - } - """, - ), - None, - None, - Template("6002600155${solc_padding}"), - id="simple", - ), - pytest.param( - ( - """ - { - sstore(1, 2) - } - """, - ), - None, - "0x00", - Template("6002600155${solc_padding}00"), - id="simple-with-padding", - ), - pytest.param( - ( - """ - { - sstore(1, 2) - } - """, - ), - "0x00", - None, - Template("006002600155${solc_padding}"), - id="simple-with-padding-2", - ), - pytest.param( - ( - """ - { - sstore(1, 2) - } - """, - """ - { - sstore(3, 4) - } - """, - ), - None, - None, - Template("6002600155${solc_padding}6004600355${solc_padding}"), - id="multiple", - ), - pytest.param( - ("{\n" + "\n".join(["sstore({0}, {0})".format(i) for i in range(5000)]) + "\n}",), - None, - None, - b"".join([b"\x60" + i.to_bytes(1, "big") + b"\x80\x55" for i in range(256)]) - + b"".join([b"\x61" + i.to_bytes(2, "big") + b"\x80\x55" for i in range(256, 5000)]), - id="large", - ), - ], - indirect=["yul_code", "expected_bytes"], -) -def test_yul( # noqa: D103 - yul_code: SupportsBytes, expected_bytes: bytes, padding_before: str, padding_after: str -): - assert bytes(yul_code) == expected_bytes - - @pytest.mark.parametrize( "initcode,bytecode", [ diff --git a/src/pytest_plugins/filler/filler.py b/src/pytest_plugins/filler/filler.py index 36899011706..bfe3b3674d7 100644 --- a/src/pytest_plugins/filler/filler.py +++ b/src/pytest_plugins/filler/filler.py @@ -775,6 +775,11 @@ def fixture_collector( Return configured fixture collector instance used for all tests in one test module. """ + # Dynamically load the 'static_filler' and 'solc' plugins if needed + if request.config.getoption("fill_static_tests_enabled"): + request.config.pluginmanager.import_plugin("pytest_plugins.filler.static_filler") + request.config.pluginmanager.import_plugin("pytest_plugins.solc.solc") + fixture_collector = FixtureCollector( output_dir=fixture_output.directory, flat_output=fixture_output.flat_output, diff --git a/src/pytest_plugins/filler/static_filler.py b/src/pytest_plugins/filler/static_filler.py index 83e03835826..7716222bd52 100644 --- a/src/pytest_plugins/filler/static_filler.py +++ b/src/pytest_plugins/filler/static_filler.py @@ -17,8 +17,9 @@ from _pytest.python import Module from ethereum_test_fixtures import BaseFixture, LabeledFixtureFormat -from ethereum_test_forks import Fork +from ethereum_test_forks import Fork, get_closest_fork from ethereum_test_specs import BaseStaticTest, BaseTest +from ethereum_test_tools.code.yul import Yul from ..forks.forks import ValidityMarker, get_intersection_set from ..shared.helpers import labeled_format_parameter_set @@ -109,18 +110,6 @@ def get_all_combinations_from_parametrize_marks( return all_argument_names, all_value_combinations -def pytest_addoption(parser: pytest.Parser): - """Add command-line options to pytest.""" - static_filler_group = parser.getgroup("static", "Arguments defining static filler behavior") - static_filler_group.addoption( - "--fill-static-tests", - action="store_true", - dest="fill_static_tests_enabled", - default=None, - help=("Enable reading and filling from static test files."), - ) - - def pytest_collect_file(file_path: Path, parent) -> pytest.Collector | None: """Pytest hook that collects test cases from static files and fills them into test fixtures.""" fill_static_tests_enabled = parent.config.getoption("fill_static_tests_enabled") @@ -334,3 +323,45 @@ def runtest(self): def reportinfo(self): """Provide information for test reporting.""" return self.fspath, 0, f"Static file test: {self.name}" + + +@pytest.fixture +def yul(fork: Fork, request: pytest.FixtureRequest): + """ + Fixture that allows contract code to be defined with Yul code. + + This fixture defines a class that wraps the ::ethereum_test_tools.Yul + class so that upon instantiation within the test case, it provides the + test case's current fork parameter. The forks is then available for use + in solc's arguments for the Yul code compilation. + + Test cases can override the default value by specifying a fixed version + with the @pytest.mark.compile_yul_with(FORK) marker. + """ + solc_target_fork: Fork | None + marker = request.node.get_closest_marker("compile_yul_with") + assert hasattr(request.config, "solc_version"), "solc_version not set in pytest config." + if marker: + if not marker.args[0]: + pytest.fail( + f"{request.node.name}: Expected one argument in 'compile_yul_with' marker." + ) + for fork in request.config.all_forks: # type: ignore + 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.") + else: + solc_target_fork = get_closest_fork(fork, request.config.solc_version) + assert solc_target_fork is not None, "No fork supports provided solc version." + if solc_target_fork != fork and request.config.getoption("verbose") >= 1: + warnings.warn( + f"Compiling Yul for {solc_target_fork.name()}, not {fork.name()}.", stacklevel=2 + ) + + class YulWrapper(Yul): + def __new__(cls, *args, **kwargs): + return super(YulWrapper, cls).__new__(cls, *args, **kwargs, fork=solc_target_fork) + + return YulWrapper diff --git a/src/pytest_plugins/help/tests/test_help.py b/src/pytest_plugins/help/tests/test_help.py index 8731fd4bae8..642acf80d00 100644 --- a/src/pytest_plugins/help/tests/test_help.py +++ b/src/pytest_plugins/help/tests/test_help.py @@ -5,7 +5,6 @@ FILL_TEST_ARGS = ( "--evm-bin", "--traces", - "--solc-bin", "--filler-path", "--output", "--forks", diff --git a/src/pytest_plugins/shared/execute_fill.py b/src/pytest_plugins/shared/execute_fill.py index db0693ea6a8..be14c5d0359 100644 --- a/src/pytest_plugins/shared/execute_fill.py +++ b/src/pytest_plugins/shared/execute_fill.py @@ -1,19 +1,12 @@ """Shared pytest fixtures and hooks for EEST generation modes (fill and execute).""" -import warnings from typing import List import pytest from ethereum_test_execution import BaseExecute, LabeledExecuteFormat from ethereum_test_fixtures import BaseFixture, LabeledFixtureFormat -from ethereum_test_forks import ( - Fork, - get_closest_fork_with_solc_support, - get_forks_with_solc_support, -) from ethereum_test_specs import BaseTest -from ethereum_test_tools import Yul from pytest_plugins.spec_version_checker.spec_version_checker import EIPSpecTestItem @@ -102,49 +95,6 @@ def pytest_configure(config: pytest.Config): ) -@pytest.fixture -def yul(fork: Fork, request: pytest.FixtureRequest): - """ - Fixture that allows contract code to be defined with Yul code. - - This fixture defines a class that wraps the ::ethereum_test_tools.Yul - class so that upon instantiation within the test case, it provides the - test case's current fork parameter. The forks is then available for use - in solc's arguments for the Yul code compilation. - - Test cases can override the default value by specifying a fixed version - with the @pytest.mark.compile_yul_with(FORK) marker. - """ - solc_target_fork: Fork | None - marker = request.node.get_closest_marker("compile_yul_with") - assert hasattr(request.config, "solc_version"), "solc_version not set in pytest config." - if marker: - if not marker.args[0]: - pytest.fail( - f"{request.node.name}: Expected one argument in 'compile_yul_with' marker." - ) - for fork in request.config.all_forks: # type: ignore - 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) - assert solc_target_fork is not None, "No fork supports provided solc version." - if solc_target_fork != fork and request.config.getoption("verbose") >= 1: - warnings.warn( - f"Compiling Yul for {solc_target_fork.name()}, not {fork.name()}.", stacklevel=2 - ) - - class YulWrapper(Yul): - def __new__(cls, *args, **kwargs): - return super(YulWrapper, cls).__new__(cls, *args, **kwargs, fork=solc_target_fork) - - return YulWrapper - - @pytest.fixture(scope="function") def test_case_description(request: pytest.FixtureRequest) -> str: """Fixture to extract and combine docstrings from the test class and the test function.""" @@ -198,3 +148,15 @@ def __init__(self, message): + "properly generate a test: " + ", ".join(SPEC_TYPES_PARAMETERS) ) + + +def pytest_addoption(parser: pytest.Parser): + """Add command-line options to pytest.""" + static_filler_group = parser.getgroup("static", "Arguments defining static filler behavior") + static_filler_group.addoption( + "--fill-static-tests", + action="store_true", + dest="fill_static_tests_enabled", + default=None, + help=("Enable reading and filling from static test files."), + ) diff --git a/src/pytest_plugins/solc/solc.py b/src/pytest_plugins/solc/solc.py index 92ff69f2751..f134a45a7a9 100644 --- a/src/pytest_plugins/solc/solc.py +++ b/src/pytest_plugins/solc/solc.py @@ -1,19 +1,13 @@ -"""Pytest plugin for configuring and installing the solc compiler.""" +"""Pytest plugin for configuring and verifying the solc compiler.""" -import platform import subprocess -from argparse import ArgumentTypeError from shutil import which import pytest -import solc_select.solc_select as solc_select # type: ignore from pytest_metadata.plugin import metadata_key # type: ignore from semver import Version -from ethereum_test_forks import Frontier -from ethereum_test_tools.code import Solc - -DEFAULT_SOLC_VERSION = "0.8.24" +SOLC_EXPECTED_MIN_VERSION: Version = Version.parse("0.8.24") def pytest_addoption(parser: pytest.Parser): @@ -26,69 +20,81 @@ def pytest_addoption(parser: pytest.Parser): type=str, default=None, help=( - "Path to a solc executable (for Yul source compilation). " - "No default; if unspecified `--solc-version` is used." + "Path to a solc executable (for Yul source compilation). Default: solc binary in PATH." ), ) - solc_group.addoption( - "--solc-version", - action="store", - dest="solc_version", - default=None, - help=f"Version of the solc compiler to use. Default: {DEFAULT_SOLC_VERSION}.", - ) @pytest.hookimpl(tryfirst=True) def pytest_configure(config: pytest.Config): - """ - Ensure that the specified solc version is: - - available if --solc_bin has been specified, - - installed via solc_select if --solc_version has been specified. - """ + """Ensure that solc is available and get its version.""" solc_bin = config.getoption("solc_bin") - solc_version = config.getoption("solc_version") - - if solc_bin and solc_version: - raise pytest.UsageError( - "You cannot specify both --solc-bin and --solc-version. Please choose one." - ) + # Use provided solc binary or find it in PATH if solc_bin: - # will raise an error if the solc binary is not found. - solc_version_semver = Solc(config.getoption("solc_bin")).version + if not which(solc_bin): + pytest.exit( + f"Specified solc binary not found: {solc_bin}", + returncode=pytest.ExitCode.USAGE_ERROR, + ) else: - # if no solc binary is specified, use solc-select - solc_version = solc_version or DEFAULT_SOLC_VERSION - try: - version, _ = solc_select.current_version() - except ArgumentTypeError: - version = None - if version != solc_version: - # solc-select current does not support ARM linux - if platform.system().lower() == "linux" and platform.machine().lower() == "aarch64": - error_message = ( - f"Version {version} does not match solc_version {solc_version} " - "and since solc-select currently does not support ARM linux you must " - "manually do the following: " - "Build solc from source, and manually move the binary to " - ".venv/.solc-select/artifacts/solc-x.y.z/solc-x.y.z, then run " - "'uv run solc-select use '" - ) - pytest.exit(error_message, returncode=pytest.ExitCode.USAGE_ERROR) - - if config.getoption("verbose") > 0: - print(f"Setting solc version {solc_version} via solc-select...") - try: - solc_select.switch_global_version(solc_version, always_install=True) - except Exception as e: - message = f"Failed to install solc version {solc_version}: {e}. " - if isinstance(e, ArgumentTypeError): - message += "\nList available versions using `uv run solc-select install`." - pytest.exit(message, returncode=pytest.ExitCode.USAGE_ERROR) - solc_version_semver = Version.parse(solc_version) - config.option.solc_bin = which("solc") # save for fixture + solc_bin = which("solc") + if not solc_bin: + pytest.exit( + "solc binary not found in PATH. Please install solc and ensure it's in your PATH.", + returncode=pytest.ExitCode.USAGE_ERROR, + ) + + # Get solc version using subprocess + try: + result = subprocess.run( + [solc_bin, "--version"], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + check=True, + ) + except subprocess.CalledProcessError as e: + pytest.exit( + f"Failed to get solc version. Command output: {e.stdout}", + returncode=pytest.ExitCode.USAGE_ERROR, + ) + except subprocess.TimeoutExpired: + pytest.exit("Timeout while getting solc version.", returncode=pytest.ExitCode.USAGE_ERROR) + except Exception as e: + pytest.exit( + f"Unexpected error while getting solc version: {e}", + returncode=pytest.ExitCode.USAGE_ERROR, + ) + + # Parse version from output + version_output = result.stdout + version_line = None + + # Look for version in output (format: "Version: X.Y.Z+commit.hash") + for line in version_output.split("\n"): + if line.startswith("Version:"): + version_line = line + break + + if not version_line: + pytest.exit( + f"Could not parse solc version from output:\n{version_output}", + returncode=pytest.ExitCode.USAGE_ERROR, + ) + # Extract version number + try: + # --version format is typically something like "0.8.24+commit.e11b9ed9.Linux.g++" + version_str = version_line.split()[1].split("+")[0] + solc_version_semver = Version.parse(version_str) + except (IndexError, ValueError) as e: + pytest.exit( + f"Failed to parse solc version from: {version_line}\nError: {e}", + returncode=pytest.ExitCode.USAGE_ERROR, + ) + + # Store version in metadata if "Tools" not in config.stash[metadata_key]: config.stash[metadata_key]["Tools"] = { "solc": str(solc_version_semver), @@ -96,40 +102,27 @@ def pytest_configure(config: pytest.Config): else: config.stash[metadata_key]["Tools"]["solc"] = str(solc_version_semver) - if solc_version_semver < Frontier.solc_min_version(): + # Check minimum version requirement + solc_version_semver = Version.parse(str(solc_version_semver).split()[0].split("-")[0]) + if solc_version_semver < SOLC_EXPECTED_MIN_VERSION: pytest.exit( - f"Unsupported solc version: {solc_version}. Minimum required version is " - f"{Frontier.solc_min_version()}", + f"Unsupported solc version: {solc_version_semver}. Minimum required version is " + f"{SOLC_EXPECTED_MIN_VERSION}", returncode=pytest.ExitCode.USAGE_ERROR, ) + + # Store for later use config.solc_version = solc_version_semver # type: ignore + config.option.solc_bin = solc_bin # save for fixture - # test whether solc_version matches actual one - # using subprocess because that's how yul is compiled in - # ./src/ethereum_test_specs/static_state/common/compile_yul.py - expected_solc_version_string: str = str(solc_version_semver) - actual_solc_version = subprocess.run( - ["solc", "--version"], - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, - check=True, - ) - actual_solc_version_string = actual_solc_version.stdout - # use only look at first 10 chars to pass e.g. - # actual: 0.8.25+commit.b61c2a91.Linux.g++ should pass with expected: "0.8.25+commit.b61c2a91 - if ( - expected_solc_version_string[:10] not in actual_solc_version_string - ) or expected_solc_version_string == "": - error_message = f"Expected solc version {solc_version_semver} but detected a\ - different solc version:\n{actual_solc_version_string}\nCritical error, aborting.." - pytest.exit(error_message, returncode=pytest.ExitCode.USAGE_ERROR) + if config.getoption("verbose") > 0: + print(f"Using solc version {solc_version_semver} from {solc_bin}") @pytest.fixture(autouse=True, scope="session") def solc_bin(request: pytest.FixtureRequest): """Return configured solc binary path.""" - return request.config.getoption("solc_bin") + return request.config.getoption("solc_bin") or which("solc") @pytest.hookimpl(trylast=True) @@ -138,4 +131,5 @@ def pytest_report_header(config, start_path): if config.option.collectonly: return solc_version = config.stash[metadata_key]["Tools"]["solc"] - return [(f"solc: {solc_version}")] + solc_path = config.option.solc_bin or which("solc") + return [f"solc: {solc_version}", f"solc path: {solc_path}"] diff --git a/src/pytest_plugins/solc/tests/__init__.py b/src/pytest_plugins/solc/tests/__init__.py deleted file mode 100644 index e6ea310c032..00000000000 --- a/src/pytest_plugins/solc/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Tests for the solc pytest plugin.""" diff --git a/src/pytest_plugins/solc/tests/test_solc.py b/src/pytest_plugins/solc/tests/test_solc.py deleted file mode 100644 index 6463fc0ab13..00000000000 --- a/src/pytest_plugins/solc/tests/test_solc.py +++ /dev/null @@ -1,185 +0,0 @@ -"""Tests for the solc plugin.""" - -import os -import shutil -import tempfile -from pathlib import Path - -import pytest -import solc_select.constants # type: ignore -import solc_select.solc_select # type: ignore - -pytestmark = [pytest.mark.run_in_serial] - - -@pytest.fixture(scope="module") -def create_clean_solc_select_environment(request): - """ - On Setup: Copies solc artifacts to a temporary location before starting tests - Teardown: Restores the artifacts after tests are done. - """ - try: - test_session_solc_version, _ = solc_select.solc_select.current_version() - except Exception as e: - raise Exception( - "Error in setup: ensure you've called `solc-select use ` before running the " - f"framework tests (exception: {e})" - ) from e - - artifacts_dir = solc_select.constants.ARTIFACTS_DIR - global_version_file = solc_select.constants.SOLC_SELECT_DIR.joinpath("global-version") - - with tempfile.TemporaryDirectory() as temp_dir: - temp_dir_path = Path(temp_dir) - - # Copy global-version and artifacts to a temporary directory and delete them - if global_version_file.exists(): - shutil.copy(global_version_file, temp_dir_path / "global-version") - os.remove(global_version_file) - if artifacts_dir.exists(): - shutil.copytree(artifacts_dir, temp_dir_path / "artifacts", dirs_exist_ok=True) - shutil.rmtree(artifacts_dir) - - os.makedirs(artifacts_dir, exist_ok=True) # this won't get recreated by solc-select - - yield - # Teardown: Restore the original files and directory from the temporary location - if global_version_file.exists(): - os.remove(global_version_file) - if artifacts_dir.exists(): - shutil.rmtree(artifacts_dir) - - # Restore the global-version file and artifacts - if (temp_dir_path / "global-version").exists(): - shutil.copy(temp_dir_path / "global-version", global_version_file) - if (temp_dir_path / "artifacts").exists(): - shutil.copytree(temp_dir_path / "artifacts", artifacts_dir, dirs_exist_ok=True) - - # Restore the solc version - solc_select.solc_select.switch_global_version( - str(test_session_solc_version), always_install=True - ) - - -@pytest.mark.usefixtures("create_clean_solc_select_environment") -@pytest.mark.parametrize("solc_version", ["0.8.21", "0.8.26"]) -class TestSolcVersion: # noqa: D101 - def test_solc_versions_flag(self, pytester, solc_version): - """Ensure that the version specified by the `--solc-version` gets installed and is used.""" - pytester.makeconftest( - f""" - import pytest - from ethereum_test_tools.code import Solc - - @pytest.fixture(autouse=True) - def check_solc_version(request, solc_bin): - assert request.config.getoption("solc_version") == "{solc_version}" - assert Solc(solc_bin).version == "{solc_version}" - """ - ) - pytester.copy_example(name="pytest.ini") - pytester.copy_example(name="tests/homestead/yul/test_yul_example.py") - result = pytester.runpytest( - "-v", - "--fork=Homestead", - "--flat-output", # required as copy_example doesn't copy to "tests/"" sub-folder - "-m", - "state_test", - f"--solc-version={solc_version}", - ) - - result.assert_outcomes( - passed=1, - failed=0, - skipped=0, - errors=0, - ) - - -def test_solc_version_too_old(pytester): - """Test the plugin exits with a UsageError if a version prior to Frontier is specified.""" - old_solc_version = "0.8.19" - pytester.copy_example(name="pytest.ini") - test_path = pytester.copy_example(name="tests/homestead/yul/test_yul_example.py") - result = pytester.runpytest( - "-v", "--fork=Frontier", "--solc-version", old_solc_version, test_path - ) - assert result.ret == pytest.ExitCode.USAGE_ERROR - assert "Unsupported solc version" in "\n".join(result.stderr.lines) - - -def test_unknown_solc_version(pytester): - """Test the plugin exits with a UsageError if a version unknown to solc-select is specified.""" - unknown_solc_version = "420.69.0" - pytester.copy_example(name="pytest.ini") - test_path = pytester.copy_example(name="tests/homestead/yul/test_yul_example.py") - result = pytester.runpytest( - "-v", "--fork=Frontier", "--solc-version", unknown_solc_version, test_path - ) - assert result.ret == pytest.ExitCode.USAGE_ERROR - stderr = "\n".join(result.stderr.lines) - assert f"Unknown version '{unknown_solc_version}'" in stderr - assert "List available versions using" in stderr - - -def test_bad_solc_flag_combination(pytester): - """ - Test the plugin exits with a UsageError if both `--solc-bin` and `--solc-version` are - specified. - """ - pytester.copy_example(name="pytest.ini") - test_path = pytester.copy_example(name="tests/homestead/yul/test_yul_example.py") - result = pytester.runpytest( - "-v", "--fork=Frontier", "--solc-version=0.8.24", "--solc-bin=solc", test_path - ) - assert result.ret == pytest.ExitCode.USAGE_ERROR - assert "You cannot specify both --solc-bin and --solc-version" in "\n".join( - result.stderr.lines - ) - - -@pytest.mark.usefixtures("create_clean_solc_select_environment") -class TestSolcBin: - """Test the `--solc-bin` flag.""" - - @pytest.fixture() - def solc_version(self): # noqa: D102 - return "0.8.25" - - @pytest.fixture() - def solc_bin(self, solc_version): - """Return available solc binary.""" - solc_select.solc_select.switch_global_version(solc_version, always_install=True) - bin_path = Path(f"solc-{solc_version}") / f"solc-{solc_version}" - return solc_select.constants.ARTIFACTS_DIR.joinpath(bin_path) - - def test_solc_bin(self, pytester, solc_version, solc_bin): - """Ensure that the version specified by the `--solc-version` gets installed and is used.""" - pytester.makeconftest( - f""" - import pytest - from ethereum_test_tools.code import Solc - - @pytest.fixture(autouse=True) - def check_solc_version(request, solc_bin): - # test via solc_bin fixture - assert Solc(solc_bin).version == "{solc_version}" - """ - ) - pytester.copy_example(name="pytest.ini") - pytester.copy_example(name="tests/homestead/yul/test_yul_example.py") - result = pytester.runpytest( - "-v", - "--fork=Homestead", - "-m", - "state_test", - "--flat-output", # required as copy_example doesn't copy to "tests/"" sub-folder, - f"--solc-bin={solc_bin}", - ) - - result.assert_outcomes( - passed=1, - failed=0, - skipped=0, - errors=0, - ) diff --git a/tests/cancun/eip6780_selfdestruct/test_selfdestruct_revert.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct_revert.py index e67383e38b1..e8f1d0b3a9a 100644 --- a/tests/cancun/eip6780_selfdestruct/test_selfdestruct_revert.py +++ b/tests/cancun/eip6780_selfdestruct/test_selfdestruct_revert.py @@ -16,7 +16,6 @@ StateTestFiller, Storage, Transaction, - YulCompiler, compute_create_address, ) from ethereum_test_tools.vm.opcode import Opcodes as Op @@ -60,9 +59,7 @@ def selfdestruct_on_outer_call() -> int: @pytest.fixture def recursive_revert_contract_code( - yul: YulCompiler, selfdestruct_on_outer_call: int, - selfdestruct_with_transfer_contract_code: Bytecode, selfdestruct_with_transfer_contract_address: Address, ) -> Bytecode: """ @@ -71,59 +68,173 @@ def recursive_revert_contract_code( Then, recurse into a new call which transfers value to A, call A.selfdestruct, and reverts. """ - optional_outer_call_code_1 = "" - optional_outer_call_code_2 = "" - optional_outer_call_code = f""" - mstore(0, 1) - pop(call(gaslimit(), {selfdestruct_with_transfer_contract_address}, 1, 0, 32, 0, 0))""" + # Common prefix for all three cases: + # case 1: selfdestruct_on_outer_call=1 + # case 2: selfdestruct_on_outer_call=2 + # case 3: selfdestruct_on_outer_call has a different value + common_prefix = ( + Op.PUSH0 + + Op.CALLDATALOAD + + Op.PUSH1(0x1) + + Op.PUSH20(selfdestruct_with_transfer_contract_address) + + Op.SWAP2 + + Op.SWAP1 + + Op.DUP2 + + Op.PUSH0 + + Op.EQ + + Op.PUSH1(0x3A) + + Op.JUMPI + + Op.POP + + Op.PUSH1(0x1) + + Op.EQ + + Op.PUSH1(0x29) + + Op.JUMPI + + Op.STOP + + Op.JUMPDEST + + Op.PUSH0 + + Op.PUSH1(0x20) + + Op.DUP2 + + Op.PUSH1(0x1) + + Op.DUP2 + + Op.SWAP5 + + Op.DUP2 + + Op.DUP4 + + Op.MSTORE + + Op.GASLIMIT + + Op.CALL + + Op.PUSH0 + + Op.DUP1 + + Op.REVERT + + Op.JUMPDEST + ) + if selfdestruct_on_outer_call == 1: - optional_outer_call_code_1 = optional_outer_call_code + suffix = ( + Op.SWAP1 + + Op.POP + + Op.PUSH1(0x1) + + Op.PUSH0 + + Op.MSTORE + + Op.PUSH0 + + Op.DUP1 + + Op.PUSH1(0x20) + + Op.DUP2 + + Op.PUSH1(0x1) + + Op.DUP7 + + Op.GASLIMIT + + Op.CALL + + Op.POP + + Op.PUSH0 + + Op.DUP1 + + Op.MSTORE + + Op.PUSH0 + + Op.DUP1 + + Op.PUSH1(0x20) + + Op.DUP2 + + Op.PUSH1(0x1) + + Op.DUP7 + + Op.GASLIMIT + + Op.CALL + + Op.POP + + Op.PUSH0 + + Op.MSTORE + + Op.PUSH0 + + Op.DUP1 + + Op.PUSH1(0x20) + + Op.DUP2 + + Op.DUP1 + + Op.ADDRESS + + Op.GASLIMIT + + Op.CALL + + Op.POP + + Op.BALANCE + + Op.PUSH1(0x1) + + Op.SSTORE + + Op.PUSH0 + + Op.DUP1 + + Op.RETURN + ) elif selfdestruct_on_outer_call == 2: - optional_outer_call_code_2 = optional_outer_call_code - - """Contract code which calls selfdestructable contract, and also makes use of revert""" - return yul( - f""" - {{ - let operation := calldataload(0) - let op_outer_call := 0 - let op_inner_call := 1 - - switch operation - case 0 /* outer call */ {{ - // transfer value to contract and make it selfdestruct - {optional_outer_call_code_1} - - // transfer value to the selfdestructed contract - mstore(0, 0) - pop(call(gaslimit(), {selfdestruct_with_transfer_contract_address}, 1, 0, 32, 0, 0)) - - // recurse into self - mstore(0, op_inner_call) - pop(call(gaslimit(), address(), 0, 0, 32, 0, 0)) - - // store the selfdestructed contract's balance for verification - sstore(1, balance({selfdestruct_with_transfer_contract_address})) - - // transfer value to contract and make it selfdestruct - {optional_outer_call_code_2} - - return(0, 0) - }} - case 1 /* inner call */ {{ - // trigger previously-selfdestructed contract to self destruct - // and then revert - - mstore(0, 1) - pop(call(gaslimit(), {selfdestruct_with_transfer_contract_address}, 1, 0, 32, 0, 0)) - revert(0, 0) - }} - default {{ - stop() - }} - }} - """ # noqa: E272, E201, E202, E221, E501 - ) + suffix = ( + Op.PUSH0 + + Op.PUSH1(0x20) + + Op.DUP2 + + Op.PUSH1(0x1) + + Op.DUP7 + + Op.DUP3 + + Op.SWAP6 + + Op.DUP4 + + Op.DUP1 + + Op.MSTORE + + Op.DUP4 + + Op.DUP1 + + Op.DUP7 + + Op.DUP2 + + Op.DUP7 + + Op.DUP7 + + Op.GASLIMIT + + Op.CALL + + Op.POP + + Op.DUP4 + + Op.MSTORE + + Op.DUP3 + + Op.DUP1 + + Op.DUP6 + + Op.DUP2 + + Op.DUP1 + + Op.ADDRESS + + Op.GASLIMIT + + Op.CALL + + Op.POP + + Op.DUP1 + + Op.BALANCE + + Op.DUP3 + + Op.SSTORE + + Op.DUP2 + + Op.DUP4 + + Op.MSTORE + + Op.GASLIMIT + + Op.CALL + + Op.PUSH0 + + Op.DUP1 + + Op.RETURN + ) + else: # selfdestruct_on_outer_call is neither 1 nor 2 + suffix = ( + Op.SWAP1 + + Op.POP + + Op.PUSH0 + + Op.DUP1 + + Op.MSTORE + + Op.PUSH0 + + Op.DUP1 + + Op.PUSH1(0x20) + + Op.DUP2 + + Op.PUSH1(0x1) + + Op.DUP7 + + Op.GASLIMIT + + Op.CALL + + Op.POP + + Op.PUSH0 + + Op.MSTORE + + Op.PUSH0 + + Op.DUP1 + + Op.PUSH1(0x20) + + Op.DUP2 + + Op.DUP1 + + Op.ADDRESS + + Op.GASLIMIT + + Op.CALL + + Op.POP + + Op.BALANCE + + Op.PUSH1(0x1) + + Op.SSTORE + + Op.PUSH0 + + Op.DUP1 + + Op.RETURN + ) + + return common_prefix + suffix @pytest.fixture @@ -141,35 +252,43 @@ def selfdestruct_with_transfer_contract_address( @pytest.fixture -def selfdestruct_with_transfer_contract_code( - yul: YulCompiler, selfdestruct_recipient_address: Address -) -> Bytecode: +def selfdestruct_with_transfer_contract_code(selfdestruct_recipient_address: Address) -> Bytecode: """Contract that can selfdestruct and receive value.""" - return yul( - f""" - {{ - let operation := calldataload(0) - - switch operation - case 0 /* no-op used for transferring value to this contract */ {{ - let times_called := sload(0) - times_called := add(times_called, 1) - sstore(0, times_called) - return(0, 0) - }} - case 1 /* trigger the contract to selfdestruct */ {{ - let times_called := sload(1) - times_called := add(times_called, 1) - sstore(1, times_called) - selfdestruct({selfdestruct_recipient_address}) - }} - default /* unsupported operation */ {{ - stop() - }} - }} - """ # noqa: E272, E201, E202, E221 + code: Bytecode = ( + Op.PUSH0 + + Op.CALLDATALOAD + + Op.PUSH20(selfdestruct_recipient_address) + + Op.SWAP1 + + Op.EQ(Op.PUSH0, Op.DUP1) + + Op.PUSH1(0x2F) + + Op.JUMPI + + Op.PUSH1(0x1) + + Op.EQ + + Op.PUSH1(0x25) + + Op.JUMPI + + Op.STOP + + Op.JUMPDEST + + Op.PUSH1(0x1) + + Op.DUP1 + + Op.SLOAD + + Op.ADD + + Op.PUSH1(0x1) + + Op.SSTORE + + Op.SELFDESTRUCT + + Op.JUMPDEST + + Op.PUSH1(0x1) + + Op.PUSH0 + + Op.SLOAD + + Op.ADD + + Op.PUSH0 + + Op.SSTORE + + Op.PUSH0 + + Op.DUP1 + + Op.RETURN ) + return code + @pytest.fixture def selfdestruct_with_transfer_contract_initcode( diff --git a/tests/constantinople/eip1014_create2/test_recreate.py b/tests/constantinople/eip1014_create2/test_recreate.py index 0f4f8969765..702eca9bb77 100644 --- a/tests/constantinople/eip1014_create2/test_recreate.py +++ b/tests/constantinople/eip1014_create2/test_recreate.py @@ -10,7 +10,6 @@ BlockchainTestFiller, Initcode, Transaction, - Yul, compute_create2_address, ) from ethereum_test_tools import Opcodes as Op @@ -40,19 +39,15 @@ def test_recreate( creator_address = pre.deploy_contract(creator_contract_code) sender = pre.fund_eoa() - deploy_code = Yul( - """ - { - switch callvalue() - case 0 { - selfdestruct(0) - } - default { - sstore(0, callvalue()) - } - } - """, - fork=fork, + deploy_code = ( + Op.EQ(0, Op.CALLVALUE) + + Op.PUSH1(0xC) + + Op.JUMPI + + Op.SSTORE(0, Op.CALLVALUE) + + Op.STOP + + Op.JUMPDEST + + Op.PUSH1(0x0) + + Op.SELFDESTRUCT ) initcode = Initcode(deploy_code=deploy_code) diff --git a/tests/homestead/yul/test_yul_example.py b/tests/homestead/yul/test_yul_example.py deleted file mode 100644 index 45bcb8c83ff..00000000000 --- a/tests/homestead/yul/test_yul_example.py +++ /dev/null @@ -1,56 +0,0 @@ -"""Test Yul Source Code Examples.""" - -import pytest - -from ethereum_test_forks import Fork, Frontier, Homestead -from ethereum_test_tools import ( - Account, - Alloc, - Environment, - StateTestFiller, - Transaction, - YulCompiler, -) - - -@pytest.mark.valid_from("Homestead") -def test_yul(state_test: StateTestFiller, pre: Alloc, yul: YulCompiler, fork: Fork): - """Test YUL compiled bytecode.""" - env = Environment() - - contract_address = pre.deploy_contract( - code=yul( - """ - { - function f(a, b) -> c { - c := add(a, b) - } - - sstore(0, f(1, 2)) - return(0, 32) - } - """ - ), - balance=0x0BA1A9CE0BA1A9CE, - ) - sender = pre.fund_eoa(amount=0x0BA1A9CE0BA1A9CE) - - tx = Transaction( - ty=0x0, - chain_id=0x01, - sender=sender, - to=contract_address, - gas_limit=500000, - gas_price=10, - protected=False if fork in [Frontier, Homestead] else True, - ) - - post = { - contract_address: Account( - storage={ - 0x00: 0x03, - }, - ), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tox.ini b/tox.ini index 92cd0fab845..864b5229beb 100644 --- a/tox.ini +++ b/tox.ini @@ -70,7 +70,6 @@ setenv = extras = test lint # Required `gentest` for formatting tests -commands_pre = solc-select use {[testenv]solc_version} --always-install commands = pytest -c ./pytest-framework.ini -n auto -m "not run_in_serial" pytest -c ./pytest-framework.ini -m run_in_serial @@ -85,12 +84,10 @@ description = Fill test cases in ./tests/ for deployed mainnet forks, except for setenv = # Use custom EELS_RESOLUTIONS_FILE if it is set via the environment (eg, in CI) EELS_RESOLUTIONS_FILE = {env:EELS_RESOLUTIONS_FILE:} -commands_pre = solc-select use {[testenv]solc_version} --always-install commands = pytest -n auto -m "not slow and not benchmark" --skip-evm-dump --output=/tmp/fixtures-tox --clean [testenv:tests-deployed-benchmark] description = Fill benchmarking test cases in ./tests/ for deployed mainnet forks, using evmone-t8n. -commands_pre = solc-select use {[testenv]solc_version} --always-install commands = pytest -n auto -m "benchmark" --skip-evm-dump --block-gas-limit 36000000 --output=/tmp/fixtures-tox --clean --evm-bin=evmone-t8n [testenv:tests-develop] @@ -98,7 +95,6 @@ description = Fill test cases in ./tests/ for deployed and development mainnet f setenv = # Use custom EELS_RESOLUTIONS_FILE if it is set via the environment (eg, in CI) EELS_RESOLUTIONS_FILE = {env:EELS_RESOLUTIONS_FILE:} -commands_pre = solc-select use {[testenv]solc_version} --always-install commands = pytest -n auto --until={[forks]develop} -k "not slow and not benchmark" --skip-evm-dump --output=/tmp/fixtures-tox --clean # ---------------------------------------------------------------------------------------------- diff --git a/uv.lock b/uv.lock index 7410859c376..bcd86c75b51 100644 --- a/uv.lock +++ b/uv.lock @@ -177,15 +177,6 @@ version = "2.1.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/55/df/f6db8e83bd4594c1ea685cd37fb81d5399e55765aae16d1a8a9502598f4e/ckzg-2.1.1.tar.gz", hash = "sha256:d6b306b7ec93a24e4346aa53d07f7f75053bc0afc7398e35fa649e5f9d48fcc4", size = 1120500, upload-time = "2025-03-31T21:24:12.324Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/33/4b/cd25e857cdf46a752e97c530fe2582fae77c4d16c29fff5a15b7a998e2fd/ckzg-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4b9825a1458219e8b4b023012b8ef027ef1f47e903f9541cbca4615f80132730", size = 116377, upload-time = "2025-03-31T21:22:26.952Z" }, - { url = "https://files.pythonhosted.org/packages/7e/bc/5dfef36589545f797245ecacb54ed2acfa75507f63cfe12182d1277a88f1/ckzg-2.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e2a40a3ba65cca4b52825d26829e6f7eb464aa27a9e9efb6b8b2ce183442c741", size = 100208, upload-time = "2025-03-31T21:22:28.04Z" }, - { url = "https://files.pythonhosted.org/packages/b7/52/96f0e3affbed321dc52b9b4ca13e0fb594da572d1f8edc47378fe48d8e9a/ckzg-2.1.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a1d753fbe85be7c21602eddc2d40e0915e25fce10329f4f801a0002a4f886cc7", size = 174800, upload-time = "2025-03-31T21:22:29.719Z" }, - { url = "https://files.pythonhosted.org/packages/dc/21/b1bc07cc8e5ed32817e89b054e2399d38775d92ff2d55e24bf233f537c02/ckzg-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d76b50527f1d12430bf118aff6fa4051e9860eada43f29177258b8d399448ea", size = 160847, upload-time = "2025-03-31T21:22:30.973Z" }, - { url = "https://files.pythonhosted.org/packages/c9/5a/97b173d4ff9bce798031beb12b340c4f1729eaaddd07f69f368f843db28e/ckzg-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44c8603e43c021d100f355f50189183135d1df3cbbddb8881552d57fbf421dde", size = 169712, upload-time = "2025-03-31T21:22:31.881Z" }, - { url = "https://files.pythonhosted.org/packages/7b/52/48be78c07f362438e189e2fbea7df8543290c3ee99845442549c8dc5405b/ckzg-2.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:38707a638c9d715b3c30b29352b969f78d8fc10faed7db5faf517f04359895c0", size = 172942, upload-time = "2025-03-31T21:22:32.8Z" }, - { url = "https://files.pythonhosted.org/packages/13/42/3cfcd6cbdfb9030b9071d5e413a458f93883e47ad4a7d8d4c1d57608e57d/ckzg-2.1.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:52c4d257bdcbe822d20c5cd24c8154ec5aac33c49a8f5a19e716d9107a1c8785", size = 187707, upload-time = "2025-03-31T21:22:33.673Z" }, - { url = "https://files.pythonhosted.org/packages/eb/54/d43bc3a2de486fb8be29ffedc3ec80f5726765ee4fa78beabe2ab2440f93/ckzg-2.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1507f7bfb9bcf51d816db5d8d0f0ed53c8289605137820d437b69daea8333e16", size = 182505, upload-time = "2025-03-31T21:22:34.563Z" }, - { url = "https://files.pythonhosted.org/packages/21/43/5bcd2b7630732b532006572fbb8d64a29f69530c630ae4811167a2a0dc3b/ckzg-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:d02eaaf4f841910133552b3a051dea53bcfe60cd98199fc4cf80b27609d8baa2", size = 98822, upload-time = "2025-03-31T21:22:35.786Z" }, { url = "https://files.pythonhosted.org/packages/95/2c/44120b2d9dcb0246d67a1f28b9eaa625c499014d4d42561467e28eedd285/ckzg-2.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:465e2b71cf9dc383f66f1979269420a0da9274a3a9e98b1a4455e84927dfe491", size = 116378, upload-time = "2025-03-31T21:22:36.96Z" }, { url = "https://files.pythonhosted.org/packages/23/88/c5b89ba9a730fee5e089be9e0c7048fb6707c1a0e4b6c30fcf725c3eef44/ckzg-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ee2f26f17a64ad0aab833d637b276f28486b82a29e34f32cf54b237b8f8ab72d", size = 100202, upload-time = "2025-03-31T21:22:37.799Z" }, { url = "https://files.pythonhosted.org/packages/ee/11/b0a473e80346db52ad9a629bc9fd8f773c718ed78932ea3a70392306ffc3/ckzg-2.1.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99cc2c4e9fb8c62e3e0862c7f4df9142f07ba640da17fded5f6e0fd09f75909f", size = 175595, upload-time = "2025-03-31T21:22:39.013Z" }, @@ -213,12 +204,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2e/f0/aff87c3ed80713453cb6c84fe6fbb7582d86a7a5e4460fda2a497d47f489/ckzg-2.1.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9f5556e6675866040cc4335907be6c537051e7f668da289fa660fdd8a30c9ddb", size = 188722, upload-time = "2025-03-31T21:23:02.966Z" }, { url = "https://files.pythonhosted.org/packages/44/d9/1f08bfb8fd1cbb8c7513e7ad3fb76bbb5c3fb446238c1eba582276e4d905/ckzg-2.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:55b2ba30c5c9daac0c55f1aac851f1b7bf1f7aa0028c2db4440e963dd5b866d6", size = 183686, upload-time = "2025-03-31T21:23:03.905Z" }, { url = "https://files.pythonhosted.org/packages/a3/ff/434f6d2893cbdfad00c20d17e9a52d426ca042f5e980d5c3db96bc6b6e15/ckzg-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:10d201601fc8f28c0e8cec3406676797024dd374c367bbeec5a7a9eac9147237", size = 98817, upload-time = "2025-03-31T21:23:05.2Z" }, - { url = "https://files.pythonhosted.org/packages/30/b3/a0c7d7ba6e669cf04605dc0329173db62fc1fe3c488761755cc01e5e1b4d/ckzg-2.1.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:375918e25eafb9bafe5215ab91698504cba3fe51b4fe92f5896af6c5663f50c6", size = 113191, upload-time = "2025-03-31T21:23:40.646Z" }, - { url = "https://files.pythonhosted.org/packages/f2/b9/a6cf403b8528d18d7d9154e28381a397bf466c86aa8e0b3327cffdde5749/ckzg-2.1.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:38b3b7802c76d4ad015db2b7a79a49c193babae50ee5f77e9ac2865c9e9ddb09", size = 96207, upload-time = "2025-03-31T21:23:41.596Z" }, - { url = "https://files.pythonhosted.org/packages/63/6b/5ddd713d97886becb8450e3e13db891199125f722366d30d087ad5438390/ckzg-2.1.1-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:438a5009fd254ace0bc1ad974d524547f1a41e6aa5e778c5cd41f4ee3106bcd6", size = 126160, upload-time = "2025-03-31T21:23:42.553Z" }, - { url = "https://files.pythonhosted.org/packages/c7/dd/e05aecc01e62108a7579f8df5e5d38536841f50e12172f8a84677edac0fa/ckzg-2.1.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ce11cc163a2e0dab3af7455aca7053f9d5bb8d157f231acc7665fd230565d48", size = 102811, upload-time = "2025-03-31T21:23:43.494Z" }, - { url = "https://files.pythonhosted.org/packages/c6/81/6cdadd8626ac11290af3f58ae5dcffe38bd2c8f8c798dacee7475e244aac/ckzg-2.1.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b53964c07f6a076e97eaa1ef35045e935d7040aff14f80bae7e9105717702d05", size = 111328, upload-time = "2025-03-31T21:23:44.449Z" }, - { url = "https://files.pythonhosted.org/packages/36/b7/b129ff6955cd264c6ab3dbd52dd1b2759d1b121c09c03f9991e4c722c72f/ckzg-2.1.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cf085f15ae52ab2599c9b5a3d5842794bcf5613b7f58661fbfb0c5d9eac988b9", size = 98846, upload-time = "2025-03-31T21:23:45.407Z" }, { url = "https://files.pythonhosted.org/packages/7f/ba/7d9c1f9cec7e0e382653c72165896194a05743e589b1dae2aa80236aa87f/ckzg-2.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4b0c850bd6cad22ac79b2a2ab884e0e7cd2b54a67d643cd616c145ebdb535a11", size = 113188, upload-time = "2025-03-31T21:23:46.337Z" }, { url = "https://files.pythonhosted.org/packages/2f/92/9728f5ccc1c5e87c6c5ae7941250a447b61fd5a63aadbc15249e29c21bcf/ckzg-2.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:26951f36bb60c9150bbd38110f5e1625596f9779dad54d1d492d8ec38bc84e3a", size = 96208, upload-time = "2025-03-31T21:23:47.255Z" }, { url = "https://files.pythonhosted.org/packages/39/63/5e27d587bd224fee70cb66b022e7c4ef95d0e091e08ee76c25ec12094b0d/ckzg-2.1.1-pp311-pypy311_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bbe12445e49c4bee67746b7b958e90a973b0de116d0390749b0df351d94e9a8c", size = 126158, upload-time = "2025-03-31T21:23:48.195Z" }, @@ -538,7 +523,6 @@ dependencies = [ { name = "requests-unixsocket2" }, { name = "rich" }, { name = "semver" }, - { name = "solc-select" }, { name = "tenacity" }, { name = "trie" }, { name = "types-pyyaml" }, @@ -621,7 +605,6 @@ requires-dist = [ { name = "ruff", marker = "extra == 'lint'", specifier = "==0.11.8" }, { name = "semver", specifier = ">=3.0.1,<4" }, { name = "setuptools", marker = "extra == 'docs'", specifier = "==78.0.2" }, - { name = "solc-select", specifier = ">=1.0.4,<2" }, { name = "tenacity", specifier = ">8.2.0,<9" }, { name = "trie", specifier = ">=3.1.0,<4" }, { name = "types-pyyaml", specifier = ">=6.0.12.20240917,<7" }, @@ -1450,7 +1433,6 @@ dependencies = [ { name = "packaging" }, { name = "pluggy" }, ] - sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, @@ -1767,19 +1749,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a7/a5/10f97f73544edcdef54409f1d839f6049a0d79df68adbc1ceb24d1aaca42/smmap-5.0.1-py3-none-any.whl", hash = "sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da", size = 24282, upload-time = "2023-09-17T11:35:03.253Z" }, ] -[[package]] -name = "solc-select" -version = "1.0.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "packaging" }, - { name = "pycryptodome" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/60/a0/2a2bfbbab1d9bd4e1a24e3604c30b5d6f84219238f3c98f06191faf5d019/solc-select-1.0.4.tar.gz", hash = "sha256:db7b9de009af6de3a5416b80bbe5b6d636bf314703c016319b8c1231e248a6c7", size = 21307, upload-time = "2023-06-26T16:28:27.992Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/a6/e2b2529f77431bd610de0a09d633768e9538f986fa606180b3b9a4a05a89/solc_select-1.0.4-py3-none-any.whl", hash = "sha256:9a28b8a612ff18a171929d23e2ed68a6263f4e11784fc47fa81476a3219874cb", size = 20710, upload-time = "2023-06-26T16:28:26.745Z" }, -] - [[package]] name = "sortedcontainers" version = "2.4.0"