diff --git a/README.md b/README.md index 94dc4cfd22..07885a925d 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,10 @@ For further help with working with this codebase, see the [online documentation] The available test cases can be browsed in the [Test Case Reference doc](https://ethereum.github.io/execution-spec-tests/tests/). +## Installation Troubleshooting + +If you encounter issues during the installation process, please refer to the [Installation Troubleshooting](https://ethereum.github.io/execution-spec-tests/main/getting_started/installation_troubleshooting/) page. + ## Contributing Contributions and feedback are welcome. Please see the [online documentation](https://ethereum.github.io/execution-spec-tests/writing_tests/) for this repository's coding standards and help on implementing new tests. diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 76ddb5c590..24543e51fd 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -8,6 +8,7 @@ Test fixtures for use by clients are available for each release on the [Github r ### 🧪 Test Cases +- ✨ Add tests for eof container's section bytes position smart fuzzing ([#592](https://github.com/ethereum/execution-spec-tests/pull/592)). - ✨ Add `test_create_selfdestruct_same_tx_increased_nonce` which tests self-destructing a contract with a nonce > 1 ([#478](https://github.com/ethereum/execution-spec-tests/pull/478)). - ✨ Add `test_double_kill` and `test_recreate` which test resurrection of accounts killed with `SELFDESTRUCT` ([#488](https://github.com/ethereum/execution-spec-tests/pull/488)). - ✨ Add eof example valid invalid tests from ori, fetch EOF Container implementation ([#535](https://github.com/ethereum/execution-spec-tests/pull/535)). @@ -40,6 +41,7 @@ Test fixtures for use by clients are available for each release on the [Github r - ✨ Add "description" and "url" fields containing test case documentation and a source code permalink to fixtures during `fill` and use them in `consume`-generated Hive test reports ([#579](https://github.com/ethereum/execution-spec-tests/pull/579)). - ✨ Add git workflow evmone coverage script for any new lines mentioned in converted_ethereum_tests.txt ([#503](https://github.com/ethereum/execution-spec-tests/pull/503)). - ✨ Add a new covariant marker `with_all_contract_creating_tx_types` that allows automatic parametrization of a test with all contract-creating transaction types at the current executing fork ([#602](https://github.com/ethereum/execution-spec-tests/pull/602)). +- ✨ Tests are now encouraged to declare a `pre: Alloc` parameter to get the pre-allocation object for the test, and use `pre.deploy_contract` and `pre.fund_eoa` to deploy contracts and fund accounts respectively, instead of declaring the `pre` as a dictionary or modifying its contents directly (see the [state test tutorial](https://ethereum.github.io/execution-spec-tests/main/tutorials/state_transition/) for an updated example) ([#584](https://github.com/ethereum/execution-spec-tests/pull/584)). - ✨ Enable loading of [ethereum/tests/BlockchainTests](https://github.com/ethereum/tests/tree/develop/BlockchainTests) ([#596](https://github.com/ethereum/execution-spec-tests/pull/596)). ### 🔧 EVM Tools diff --git a/docs/getting_started/installation_troubleshooting.md b/docs/getting_started/installation_troubleshooting.md new file mode 100644 index 0000000000..f6c660d5dc --- /dev/null +++ b/docs/getting_started/installation_troubleshooting.md @@ -0,0 +1,35 @@ +# Installation Troubleshooting + +This page provides guidance on how to troubleshoot common issues that may arise when installing the Execution Spec Tests repository. + +## Pip Installation Issues + +### Coincurve Installation + +If you encounter an error when installing the `coincurve` package like the following: + +```bash +Stored in directory: /tmp/... + Building wheel for coincurve (pyproject.toml) ... error + error: subprocess-exited-with-error + + × Building wheel for coincurve (pyproject.toml) did not run successfully. + │ exit code: 1 + ╰─> [27 lines of output] + ... + 571 | #include + | ^~~~~~~~~~~~~~~~~~~~~~~ + compilation terminated. + error: command '/usr/bin/gcc' failed with exit code 1 + [end of output] + + note: This error originates from a subprocess, and is likely not a problem with pip. + ERROR: Failed building wheel for coincurve +``` + +You may need to install the `libsecp256k1` library. On Ubuntu, you can install this library by running the following command: + +```bash +sudo apt update +sudo apt-get install libsecp256k1-dev +``` diff --git a/docs/getting_started/quick_start.md b/docs/getting_started/quick_start.md index f1166eb643..5af386d553 100644 --- a/docs/getting_started/quick_start.md +++ b/docs/getting_started/quick_start.md @@ -88,6 +88,10 @@ The following requires a Python 3.10, 3.11 or 3.12 installation. head fixtures/blockchain_tests/berlin/eip2930_access_list/acl/access_list.json ``` +## Installation Troubleshooting + +If you encounter issues during installation, see the [Installation Troubleshooting](./installation_troubleshooting.md) guide. + ## Next Steps 1. Learn [useful command-line flags](./executing_tests_command_line.md). diff --git a/docs/navigation.md b/docs/navigation.md index 05f5a320c3..4bb2a588f9 100644 --- a/docs/navigation.md +++ b/docs/navigation.md @@ -4,6 +4,7 @@ * [Quick Start](getting_started/quick_start.md) * [VS Code Setup](getting_started/setup_vs_code.md) * [Repository Overview](getting_started/repository_overview.md) + * [Installation Troubleshooting](getting_started/installation_troubleshooting.md) * [Executing Tests at a Prompt](getting_started/executing_tests_command_line.md) * [Executing Tests in VS Code](getting_started/executing_tests_vs_code.md) * [Executing Tests for Features Under Development](getting_started/executing_tests_dev_fork.md) diff --git a/docs/tutorials/state_transition.md b/docs/tutorials/state_transition.md index 97f3e7af40..d3a6007005 100644 --- a/docs/tutorials/state_transition.md +++ b/docs/tutorials/state_transition.md @@ -31,26 +31,26 @@ Test Yul Source Code Examples 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 +from ethereum_test_forks import Fork, Frontier, Homestead from ethereum_test_tools import ( Account, + Alloc, Environment, StateTestFiller, - TestAddress, Transaction, - Yul, + YulCompiler, ) ``` 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. ```python -@pytest.mark.valid_from("Berlin") +@pytest.mark.valid_from("Homestead") ``` 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 [Berlin fork](https://ethereum.org/en/history/#berlin) and all forks after it. The framework will then execute 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 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 execute this test case for all forks in the fork range specified by the command-line arguments. !!! info "Executing the test" To execute 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): @@ -66,7 +66,7 @@ In this case, the decorator is a custom [pytest fixture](https://docs.pytest.org ``` ```python -def test_yul(state_test: StateTestFiller, fork: Fork): +def test_yul(state_test: StateTestFiller, pre: Alloc, yul: YulCompiler, fork: Fork): """ Test YUL compiled bytecode. """ @@ -79,31 +79,45 @@ The function definition ends when there is a line that is no longer indented. As !!! 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. +!!! 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). + ```python env = Environment() ``` -This line specifies that `env` is an [`Environment`](https://github.com/ethereum/execution-spec-tests/blob/main/src/ethereum_test_tools/common/types.py#L878) object, and that we just use the default parameters. +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](https://ethereum-tests.readthedocs.io/en/latest/test_filler/state_filler.html#env). +For more information, [see the static test documentation](../consuming_tests/state_test.md). #### Pre State -```python - pre = { -``` +For every test we need to define the pre-state requirements, so we are certain of what is on the "blockchain" before the test executes. +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. -Here we define the pre-state section, the one that tells us what is on the "blockchain" before the test. -It is a [dictionary](https://docs.python.org/3/tutorial/datastructures.html#dictionaries), which is the Python term for an associative array. +In this example we are using the `deploy_contract` method to deploy a contract to some address available in the pre-state. ```python - "0x1000000000000000000000000000000000000000": Account( + 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, + ) ``` -The keys of the dictionary are addresses (as strings), and the values are [`Account`](https://github.com/ethereum/execution-spec-tests/blob/main/src/ethereum_test_tools/common/types.py#L517) objects. -You can read more about address fields [in the static test documentation](https://ethereum-tests.readthedocs.io/en/latest/test_filler/state_filler.html#address-fields). +Specifically we deploy a contract with yul code that adds two numbers and stores the result in storage. ```python balance=0x0BA1A9CE0BA1A9CE, @@ -112,7 +126,21 @@ You can read more about address fields [in the static test documentation](https: 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. ```python - code=Yul( + contract_address = pre.deploy_contract( +``` + +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. + +```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. + +```python + code=yul( ``` 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 (`{ }`). @@ -120,7 +148,7 @@ Here we define the [Yul](https://docs.soliditylang.org/en/v0.8.17/yul.html) code When running the test filler `fill`, the solidity compiler `solc` will automatically translate the Yul to EVM opcode at runtime. !!! note - Currently Yul and direct EVM opcode are supported in execution spec tests. LLL and Solidity may be supported in the future. + Currently Yul and direct EVM opcode are supported in execution spec tests. ```python """ @@ -132,8 +160,6 @@ When running the test filler `fill`, the solidity compiler `solc` will automatic return(0, 32) } """ - ), - ), ``` 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`. @@ -141,36 +167,52 @@ Within this example test Yul code we have a function definition, and inside it w 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 - TestAddress: Account(balance=0x0BA1A9CE0BA1A9CE), - } + sender = pre.fund_eoa(amount=0x0BA1A9CE0BA1A9CE) ``` -[`TestAddress`](https://github.com/ethereum/execution-spec-tests/blob/main/src/ethereum_test_tools/common/constants.py#L7) is an address for which the test filler has the private key. -This means that the test runner can issue a transaction as that contract. -Of course, this address also needs a balance to be able to issue transactions. +In this line we specify that we require a single externally owned account (EOA) with a balance of `0x0BA1A9CE0BA1A9CE` Wei. + +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. #### Transactions ```python tx = Transaction( ty=0x0, - chain_id=0x0, - nonce=0, - to="0x1000000000000000000000000000000000000000", + chain_id=0x01, + sender=sender, + to=contract_address, gas_limit=500000, gas_price=10, - protected=False, + protected=False if fork in [Frontier, Homestead] else True, ) ``` -With the pre-state specified, we can add a description for the [`Transaction`](https://github.com/ethereum/execution-spec-tests/blob/main/src/ethereum_test_tools/common/types.py#L1155). -For more information, [see the static test documentation](https://ethereum-tests.readthedocs.io/en/latest/test_filler/state_filler.html#transaction) +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). + +```python + sender=sender, +``` + +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. + +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. + +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](../consuming_tests/state_test.md) #### Post State ```python post = { - "0x1000000000000000000000000000000000000000": Account( + contract_address: Account( storage={ 0x00: 0x03, }, diff --git a/src/ethereum_test_tools/__init__.py b/src/ethereum_test_tools/__init__.py index c382556121..fafd8dd30d 100644 --- a/src/ethereum_test_tools/__init__.py +++ b/src/ethereum_test_tools/__init__.py @@ -15,6 +15,7 @@ YulCompiler, ) from .common import ( + EOA, AccessList, Account, Address, @@ -99,6 +100,7 @@ "ReferenceSpec", "ReferenceSpecTypes", "Removable", + "EOA", "StateTest", "StateTestFiller", "Storage", diff --git a/src/ethereum_test_tools/common/__init__.py b/src/ethereum_test_tools/common/__init__.py index d953fb6111..1812f1111b 100644 --- a/src/ethereum_test_tools/common/__init__.py +++ b/src/ethereum_test_tools/common/__init__.py @@ -36,6 +36,7 @@ ) from .json import to_json from .types import ( + EOA, AccessList, Account, Alloc, @@ -69,10 +70,11 @@ "Number", "Removable", "Requests", + "EOA", "Storage", + "TestParameterGroup", "TestAddress", "TestAddress2", - "TestParameterGroup", "TestPrivateKey", "TestPrivateKey2", "Transaction", diff --git a/src/ethereum_test_tools/common/base_types.py b/src/ethereum_test_tools/common/base_types.py index c3f0d255d3..b54ba401b0 100644 --- a/src/ethereum_test_tools/common/base_types.py +++ b/src/ethereum_test_tools/common/base_types.py @@ -115,6 +115,8 @@ def __new__(cls, input: BytesConvertible): """ Creates a new Bytes object. """ + if type(input) is cls: + return input return super(Bytes, cls).__new__(cls, to_bytes(input)) def __hash__(self) -> int: @@ -234,6 +236,8 @@ def __new__(cls, input: FixedSizeBytesConvertible | T): """ Creates a new FixedSizeBytes object. """ + if type(input) is cls: + return input return super(FixedSizeBytes, cls).__new__(cls, to_fixed_size_bytes(input, cls.byte_length)) def __hash__(self) -> int: @@ -277,7 +281,7 @@ class Address(FixedSizeBytes[20]): # type: ignore Class that helps represent Ethereum addresses in tests. """ - pass + label: str | None = None class Hash(FixedSizeBytes[32]): # type: ignore diff --git a/src/ethereum_test_tools/common/constants.py b/src/ethereum_test_tools/common/constants.py index 14f3b0364e..2eff979a90 100644 --- a/src/ethereum_test_tools/common/constants.py +++ b/src/ethereum_test_tools/common/constants.py @@ -9,8 +9,8 @@ TestAddress = Address("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b") TestAddress2 = Address("0x8a0a19589531694250d570040a0c4b74576919b8") -TestPrivateKey = "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" -TestPrivateKey2 = "0x9e7645d0cfd9c3a04eb7a9db59a4eb7d359f2e75c9164a9d6b9a7d54e1b6a36f" +TestPrivateKey = 0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8 +TestPrivateKey2 = 0x9E7645D0CFD9C3A04EB7A9DB59A4EB7D359F2E75C9164A9D6B9A7D54E1B6A36F AddrAA = Address(0xAA) AddrBB = Address(0xBB) diff --git a/src/ethereum_test_tools/common/helpers.py b/src/ethereum_test_tools/common/helpers.py index c463b18661..0cc9d3742f 100644 --- a/src/ethereum_test_tools/common/helpers.py +++ b/src/ethereum_test_tools/common/helpers.py @@ -10,6 +10,7 @@ from .base_types import Address, Bytes, Hash from .conversions import BytesConvertible, FixedSizeBytesConvertible +from .types import EOA """ Helper functions @@ -24,13 +25,17 @@ def ceiling_division(a: int, b: int) -> int: return -(a // -b) -def compute_create_address(address: FixedSizeBytesConvertible, nonce: int) -> Address: +def compute_create_address(address: FixedSizeBytesConvertible | EOA, nonce: int = 0) -> Address: """ Compute address of the resulting contract created using a transaction or the `CREATE` opcode. """ + if isinstance(address, EOA): + nonce = address.nonce + else: + address = Address(address) nonce_bytes = bytes() if nonce == 0 else nonce.to_bytes(length=1, byteorder="big") - hash = keccak256(encode([Address(address), nonce_bytes])) + hash = keccak256(encode([address, nonce_bytes])) return Address(hash[-20:]) diff --git a/src/ethereum_test_tools/common/types.py b/src/ethereum_test_tools/common/types.py index a2e6789722..0a826e72fb 100644 --- a/src/ethereum_test_tools/common/types.py +++ b/src/ethereum_test_tools/common/types.py @@ -2,8 +2,10 @@ Useful types for generating Ethereum tests. """ +import inspect from dataclasses import dataclass -from functools import cached_property +from enum import IntEnum +from functools import cache, cached_property from itertools import count from typing import ( Any, @@ -30,6 +32,7 @@ BaseModel, ConfigDict, Field, + PrivateAttr, RootModel, TypeAdapter, computed_field, @@ -55,7 +58,7 @@ NumberBoundTypeVar, ZeroPaddedHexNumber, ) -from .constants import TestPrivateKey +from .constants import TestAddress, TestPrivateKey, TestPrivateKey2 from .conversions import BytesConvertible, FixedSizeBytesConvertible, NumberConvertible @@ -192,8 +195,11 @@ def __init__(self, address: Address, key: int, want: int, got: int, *args): def __str__(self): """Print exception string""" + label_str = "" + if self.address.label is not None: + label_str = f" ({self.address.label})" return ( - f"incorrect value in address {self.address} for " + f"incorrect value in address {self.address}{label_str} for " + f"key {Hash(self.key)}:" + f" want {HexNumber(self.want)} (dec:{self.want})," + f" got {HexNumber(self.got)} (dec:{self.got})" @@ -368,8 +374,11 @@ def __init__(self, address: Address, want: int | None, got: int | None, *args): def __str__(self): """Print exception string""" + label_str = "" + if self.address.label is not None: + label_str = f" ({self.address.label})" return ( - f"unexpected nonce for account {self.address}: " + f"unexpected nonce for account {self.address}{label_str}: " + f"want {self.want}, got {self.got}" ) @@ -392,8 +401,11 @@ def __init__(self, address: Address, want: int | None, got: int | None, *args): def __str__(self): """Print exception string""" + label_str = "" + if self.address.label is not None: + label_str = f" ({self.address.label})" return ( - f"unexpected balance for account {self.address}: " + f"unexpected balance for account {self.address}{label_str}: " + f"want {self.want}, got {self.got}" ) @@ -416,8 +428,11 @@ def __init__(self, address: Address, want: bytes | None, got: bytes | None, *arg def __str__(self): """Print exception string""" + label_str = "" + if self.address.label is not None: + label_str = f" ({self.address.label})" return ( - f"unexpected code for account {self.address}: " + f"unexpected code for account {self.address}{label_str}: " + f"want {self.want}, got {self.got}" ) @@ -489,6 +504,87 @@ def to_kwargs_dict(account: "Dict | Account | None") -> Dict: return cls(**kwargs) +class EOA(Address): + """ + An Externally Owned Account (EOA) is an account controlled by a private key. + + The EOA is defined by its address and (optionally) by its corresponding private key. + """ + + key: Hash | None + nonce: Number + + def __new__( + cls, + address: "FixedSizeBytesConvertible | Address | EOA | None" = None, + *, + key: FixedSizeBytesConvertible | None = None, + nonce: NumberConvertible = 0, + ): + """ + Init the EOA. + """ + if address is None: + if key is None: + raise ValueError("impossible to initialize EOA without address") + private_key = PrivateKey(Hash(key)) + public_key = private_key.public_key + address = Address(keccak256(public_key.format(compressed=False)[1:])[32 - 20 :]) + elif isinstance(address, EOA): + return address + instance = super(EOA, cls).__new__(cls, address) + instance.key = Hash(key) if key is not None else None + instance.nonce = Number(nonce) + return instance + + def get_nonce(self) -> Number: + """ + Returns the current nonce of the EOA and increments it by one. + """ + nonce = self.nonce + self.nonce = Number(nonce + 1) + return nonce + + def copy(self) -> "EOA": + """ + Returns a copy of the EOA. + """ + return EOA(Address(self), key=self.key, nonce=self.nonce) + + +@cache +def eoa_by_index(i: int) -> EOA: + """ + Returns an EOA by index. + """ + return EOA(key=TestPrivateKey + i if i != 1 else TestPrivateKey2, nonce=0) + + +def eoa_iterator() -> Iterator[EOA]: + """ + Returns an iterator over EOAs copies. + """ + return iter(eoa_by_index(i).copy() for i in count()) + + +def contract_address_iterator( + start_address: int = 0x1000, increments: int = 0x100 +) -> Iterator[Address]: + """ + Returns an iterator over contract addresses. + """ + return iter(Address(start_address + (i * increments)) for i in count()) + + +class AllocMode(IntEnum): + """ + Allocation mode for the state. + """ + + PERMISSIVE = 0 + STRICT = 1 + + class Alloc(RootModel[Dict[Address, Account | None]]): """ Allocation of accounts in the state, pre and post test execution. @@ -496,6 +592,12 @@ class Alloc(RootModel[Dict[Address, Account | None]]): root: Dict[Address, Account | None] = Field(default_factory=dict, validate_default=True) + _alloc_mode: AllocMode = PrivateAttr(default=AllocMode.PERMISSIVE) + _contract_address_iterator: Iterator[Address] = PrivateAttr( + default_factory=contract_address_iterator + ) + _eoa_iterator: Iterator[EOA] = PrivateAttr(default_factory=eoa_iterator) + @dataclass(kw_only=True) class UnexpectedAccount(Exception): """ @@ -576,6 +678,14 @@ def __delitem__(self, address: Address | FixedSizeBytesConvertible): address = Address(address) self.root.pop(address, None) + def __eq__(self, other) -> bool: + """ + Returns True if both allocations are equal. + """ + if not isinstance(other, Alloc): + return False + return self.root == other.root + def __contains__(self, address: Address | FixedSizeBytesConvertible) -> bool: """ Checks if an account is in the allocation. @@ -637,6 +747,81 @@ def verify_post_alloc(self, got_alloc: "Alloc"): else: raise Alloc.MissingAccount(address) + def deploy_contract( + self, + code: BytesConvertible, + *, + storage: Storage + | Dict[StorageKeyValueTypeConvertible, StorageKeyValueTypeConvertible] = {}, + balance: NumberConvertible = 0, + nonce: NumberConvertible = 1, + address: Address | None = None, + label: str | None = None, + ) -> Address: + """ + Deploy a contract to the allocation. + + Warning: `address` parameter is a temporary solution to allow tests to hard-code the + contract address. Do NOT use in new tests as it will be removed in the future! + """ + if address is not None: + assert self._alloc_mode == AllocMode.PERMISSIVE, "address parameter is not supported" + assert address not in self, f"address {address} already in allocation" + contract_address = address + else: + contract_address = next(self._contract_address_iterator) + + if self._alloc_mode == AllocMode.STRICT: + assert Number(nonce) >= 1, "impossible to deploy contract with nonce lower than one" + + self[contract_address] = Account( + nonce=nonce, + balance=balance, + code=code, + storage=storage, + ) + if label is None: + # Try to deduce the label from the code + frame = inspect.currentframe() + if frame is not None: + caller_frame = frame.f_back + if caller_frame is not None: + code_context = inspect.getframeinfo(caller_frame).code_context + if code_context is not None: + line = code_context[0].strip() + if "=" in line: + label = line.split("=")[0].strip() + + contract_address.label = label + return contract_address + + def fund_eoa(self, amount: NumberConvertible = 10**21, label: str | None = None) -> EOA: + """ + Add a previously unused EOA to the pre-alloc with the balance specified by `amount`. + """ + eoa = next(self._eoa_iterator) + self[eoa] = Account( + nonce=0, + balance=amount, + ) + return eoa + + def fund_address(self, address: Address, amount: NumberConvertible): + """ + Fund an address with a given amount. + + If the address is already present in the pre-alloc the amount will be + added to its existing balance. + """ + if address in self: + account = self[address] + if account is not None: + current_balance = account.balance or 0 + account.balance = ZeroPaddedHexNumber(current_balance + Number(amount)) + return + + self[address] = Account(balance=amount) + class WithdrawalGeneric(CamelModel, Generic[NumberBoundTypeVar]): """ @@ -825,7 +1010,7 @@ class TransactionGeneric(BaseModel, Generic[NumberBoundTypeVar]): v: NumberBoundTypeVar | None = None r: NumberBoundTypeVar | None = None s: NumberBoundTypeVar | None = None - sender: Address | None = None + sender: EOA | None = None class TransactionFixtureConverter(CamelModel): @@ -937,6 +1122,7 @@ def model_post_init(self, __context): or self.max_fee_per_blob_gas is not None ): raise Transaction.InvalidFeePayment() + if "ty" not in self.model_fields_set: # Try to deduce transaction type from included fields if self.max_fee_per_blob_gas is not None or self.blob_kzg_commitments is not None: @@ -952,7 +1138,11 @@ def model_post_init(self, __context): raise Transaction.InvalidSignaturePrivateKey() if self.v is None and self.secret_key is None: - self.secret_key = Hash(TestPrivateKey) + if self.sender is not None: + self.secret_key = self.sender.key + else: + self.secret_key = Hash(TestPrivateKey) + self.sender = EOA(address=TestAddress, key=self.secret_key, nonce=0) # Set default values for fields that are required for certain tx types if self.ty <= 1 and self.gas_price is None: @@ -968,6 +1158,9 @@ def model_post_init(self, __context): if self.ty == 3 and self.max_fee_per_blob_gas is None: self.max_fee_per_blob_gas = 1 + if "nonce" not in self.model_fields_set and self.sender is not None: + self.nonce = HexNumber(self.sender.get_nonce()) + def with_error( self, error: List[TransactionException] | TransactionException ) -> "Transaction": @@ -1239,6 +1432,19 @@ def list_blob_versioned_hashes(input_txs: List["Transaction"]) -> List[Hash]: for blob_versioned_hash in tx.blob_versioned_hashes ] + @cached_property + def created_contract(self) -> Address: + """ + Returns the address of the contract created by the transaction. + """ + if self.to is not None: + raise ValueError("transaction is not a contract creation") + nonce_bytes = ( + bytes() if self.nonce == 0 else self.nonce.to_bytes(length=1, byteorder="big") + ) + hash = keccak256(eth_rlp.encode([self.sender, nonce_bytes])) + return Address(hash[-20:]) + class RequestBase: """ diff --git a/src/ethereum_test_tools/eof/v1/__init__.py b/src/ethereum_test_tools/eof/v1/__init__.py index 48d631b26f..20601e49dc 100644 --- a/src/ethereum_test_tools/eof/v1/__init__.py +++ b/src/ethereum_test_tools/eof/v1/__init__.py @@ -210,6 +210,10 @@ def list_header(sections: List["Section"]) -> bytes: Creates the single code header for all code sections contained in the list. """ + # Allow 'types section' to use skip_header_listing flag + if sections[0].skip_header_listing: + return b"" + if sections[0].kind not in SUPPORT_MULTI_SECTION_HEADER: return b"".join(s.header for s in sections) diff --git a/src/ethereum_test_tools/spec/eof/eof_test.py b/src/ethereum_test_tools/spec/eof/eof_test.py index 8cb9b69eb5..9985e39a3e 100644 --- a/src/ethereum_test_tools/spec/eof/eof_test.py +++ b/src/ethereum_test_tools/spec/eof/eof_test.py @@ -15,9 +15,8 @@ from ethereum_test_forks import Fork from evm_transition_tool import FixtureFormats, TransitionTool -from ...common import Account, Address, Alloc, Environment, Transaction +from ...common import Account, Alloc, Environment, Transaction from ...common.base_types import Bytes -from ...common.constants import TestAddress from ...eof.v1 import Container from ...exceptions import EOFException, EvmoneExceptionMapper from ..base.base_test import BaseFixture, BaseTest @@ -267,8 +266,10 @@ class EOFStateTest(EOFTest): tx_gas_limit: int = 10_000_000 tx_data: Bytes = Bytes(b"") + tx_sender_funding_amount: int = 1_000_000_000_000_000_000_000 env: Environment = Field(default_factory=Environment) container_post: Account = Field(default_factory=Account) + pre: Alloc | None = None supported_fixture_formats: ClassVar[List[FixtureFormats]] = [ FixtureFormats.EOF_TEST, @@ -288,22 +289,21 @@ def generate_state_test(self) -> StateTest: """ Generate the StateTest filler. """ - pre = Alloc() - container_address = Address(0x100) - pre[container_address] = Account(code=self.data, nonce=1) - pre[TestAddress] = Account(balance=1_000_000_000_000_000_000_000, nonce=0) + assert self.pre is not None, "pre must be set to generate a StateTest." + container_address = self.pre.deploy_contract(code=self.data) + sender = self.pre.fund_eoa(amount=self.tx_sender_funding_amount) tx = Transaction( - nonce=0, to=container_address, gas_limit=self.tx_gas_limit, gas_price=10, protected=False, data=self.tx_data, + sender=sender, ) post = Alloc() post[container_address] = self.container_post return StateTest( - pre=pre, + pre=self.pre, tx=tx, env=self.env, post=post, diff --git a/src/ethereum_test_tools/tests/test_code.py b/src/ethereum_test_tools/tests/test_code.py index 4d8e509cfb..83209e8908 100644 --- a/src/ethereum_test_tools/tests/test_code.py +++ b/src/ethereum_test_tools/tests/test_code.py @@ -19,7 +19,7 @@ from evm_transition_tool import FixtureFormats, GethTransitionTool from ..code import CalldataCase, Case, Code, Conditional, Initcode, Solc, Switch, Yul -from ..common import Account, Environment, Hash, TestAddress, Transaction +from ..common import Account, Alloc, Environment, Hash, Transaction from ..spec import StateTest from ..vm.opcode import Opcodes as Op from .conftest import SOLC_PADDING_VERSION @@ -644,13 +644,11 @@ def test_switch(tx_data: bytes, switch_bytecode: bytes, expected_storage: Mappin """ Test that the switch opcode macro gets executed as using the t8n tool. """ - code_address = 0x100 - pre = { - TestAddress: Account(balance=10_000_000, nonce=0), - code_address: Account(code=switch_bytecode), - } - tx = Transaction(to=code_address, data=tx_data, gas_limit=1_000_000) - post = {TestAddress: Account(nonce=1), code_address: Account(storage=expected_storage)} + pre = Alloc() + code_address = pre.deploy_contract(switch_bytecode) + sender = pre.fund_eoa(10_000_000) + tx = Transaction(to=code_address, data=tx_data, gas_limit=1_000_000, sender=sender) + post = {sender: Account(nonce=1), code_address: Account(storage=expected_storage)} state_test = StateTest( env=Environment(), pre=pre, diff --git a/src/ethereum_test_tools/tests/test_filling/test_fixtures.py b/src/ethereum_test_tools/tests/test_filling/test_fixtures.py index a084d4decb..6bcfde5389 100644 --- a/src/ethereum_test_tools/tests/test_filling/test_fixtures.py +++ b/src/ethereum_test_tools/tests/test_filling/test_fixtures.py @@ -16,7 +16,7 @@ from ... import Header from ...code import Yul -from ...common import Account, Environment, Hash, TestAddress, Transaction +from ...common import Account, Alloc, Environment, Hash, Transaction from ...exceptions import TransactionException from ...spec import BlockchainTest, StateTest from ...spec.blockchain.types import Block, Fixture, FixtureCommon @@ -39,14 +39,14 @@ def hash(request: pytest.FixtureRequest, solc_version: Version): """ if solc_version == Version.parse("0.8.20"): if request.node.funcargs["fork"] == Berlin: - return bytes.fromhex("193e550de3") + return bytes.fromhex("8abb1c1dd7") elif request.node.funcargs["fork"] == London: - return bytes.fromhex("b053deac0e") + return bytes.fromhex("1dfe81256f") else: if request.node.funcargs["fork"] == Berlin: - return bytes.fromhex("f3a35d34f6") + return bytes.fromhex("d45f416ad5") elif request.node.funcargs["fork"] == London: - return bytes.fromhex("c5fa75d7f6") + return bytes.fromhex("ccfc165586") def test_check_helper_fixtures(): @@ -79,11 +79,10 @@ def test_check_helper_fixtures(): def test_make_genesis(fork: Fork, hash: bytes): # noqa: D103 env = Environment() - pre = { - "0x1000000000000000000000000000000000000000": Account( - balance=0x0BA1A9CE0BA1A9CE, - code=Yul( - """ + pre = Alloc() + pre.deploy_contract( + Yul( + """ { function f(a, b) -> c { c := add(a, b) @@ -93,11 +92,11 @@ def test_make_genesis(fork: Fork, hash: bytes): # noqa: D103 return(0, 32) } """, - fork=fork, - ), + fork=fork, ), - TestAddress: Account(balance=0x0BA1A9CE0BA1A9CE), - } + balance=0x0BA1A9CE0BA1A9CE, + ) + pre.fund_eoa(0x0BA1A9CE0BA1A9CE) t8n = GethTransitionTool() fixture = BlockchainTest( diff --git a/src/ethereum_test_tools/tests/test_types.py b/src/ethereum_test_tools/tests/test_types.py index fe46c1fd7d..1a2badcfd9 100644 --- a/src/ethereum_test_tools/tests/test_types.py +++ b/src/ethereum_test_tools/tests/test_types.py @@ -17,7 +17,7 @@ Withdrawal, ) from ..common.base_types import Address, Bloom, Bytes, Hash, HeaderNonce, ZeroPaddedHexNumber -from ..common.constants import TestPrivateKey +from ..common.constants import TestAddress, TestAddress2, TestPrivateKey from ..common.json import to_json from ..common.types import Alloc, DepositRequest, Requests from ..exceptions import BlockException, TransactionException @@ -30,6 +30,7 @@ InvalidFixtureBlock, ) from ..spec.state.types import FixtureForkPost +from ..vm.opcode import Opcodes as Op def test_storage(): @@ -327,6 +328,9 @@ def test_empty_accounts(account: Account): ], ) def test_account_check_alloc(account: Account, alloc_dict: Dict[Any, Any], should_pass: bool): + """ + Test `Account.check_alloc` method. + """ alloc_account = Account(**alloc_dict) if should_pass: account.check_alloc(Address(1), alloc_account) @@ -345,21 +349,21 @@ def test_account_check_alloc(account: Account, alloc_dict: Dict[Any, Any], shoul id="empty_alloc", ), pytest.param( - Alloc({0x1: {"nonce": 1}}), - Alloc({0x2: {"nonce": 2}}), - Alloc({0x1: Account(nonce=1), 0x2: Account(nonce=2)}), + Alloc({0x1: {"nonce": 1}}), # type: ignore + Alloc({0x2: {"nonce": 2}}), # type: ignore + Alloc({0x1: Account(nonce=1), 0x2: Account(nonce=2)}), # type: ignore id="alloc_different_accounts", ), pytest.param( - Alloc({0x2: {"nonce": 1}}), - Alloc({"0x02": {"nonce": 2}}), - Alloc({0x2: Account(nonce=2)}), + Alloc({0x2: {"nonce": 1}}), # type: ignore + Alloc({"0x02": {"nonce": 2}}), # type: ignore + Alloc({0x2: Account(nonce=2)}), # type: ignore id="overwrite_account", ), pytest.param( - Alloc({0x2: {"balance": 1}}), - Alloc({"0x02": {"nonce": 1}}), - Alloc({0x2: Account(balance=1, nonce=1)}), + Alloc({0x2: {"balance": 1}}), # type: ignore + Alloc({"0x02": {"nonce": 1}}), # type: ignore + Alloc({0x2: Account(balance=1, nonce=1)}), # type: ignore id="mix_account", ), ], @@ -371,6 +375,36 @@ def test_alloc_append(alloc_1: Alloc, alloc_2: Alloc, expected_alloc: Alloc): assert Alloc.merge(alloc_1, alloc_2) == expected_alloc +def test_alloc_deploy_contract(): + """ + Test `Alloc.deploy_contract` functionallity. + """ + alloc = Alloc() + contract_1 = alloc.deploy_contract(Op.SSTORE(0, 1) + Op.STOP) + contract_2 = alloc.deploy_contract(Op.SSTORE(0, 2) + Op.STOP) + assert contract_1 != contract_2 + assert contract_1 in alloc + assert contract_2 in alloc + assert alloc[contract_1].code == bytes.fromhex("600160005500") + assert alloc[contract_2].code == bytes.fromhex("600260005500") + + +def test_alloc_fund_sender(): + """ + Test `Alloc.fund_eoa` functionallity. + """ + alloc = Alloc() + sender_1 = alloc.fund_eoa(10**18) + sender_2 = alloc.fund_eoa(10**18) + assert sender_1 != sender_2 + assert sender_1 in alloc + assert sender_2 in alloc + assert Address(sender_1) == TestAddress + assert Address(sender_2) == TestAddress2 + assert alloc[sender_1].balance == 10**18 + assert alloc[sender_2].balance == 10**18 + + @pytest.mark.parametrize( ["account_1", "account_2", "expected_account"], [ diff --git a/src/pytest_plugins/test_filler/test_filler.py b/src/pytest_plugins/test_filler/test_filler.py index f25eb08f5b..dd36daabb2 100644 --- a/src/pytest_plugins/test_filler/test_filler.py +++ b/src/pytest_plugins/test_filler/test_filler.py @@ -21,7 +21,8 @@ get_closest_fork_with_solc_support, get_forks_with_solc_support, ) -from ethereum_test_tools import SPEC_TYPES, BaseTest, FixtureCollector, TestInfo, Yul +from ethereum_test_tools import SPEC_TYPES, Alloc, BaseTest, FixtureCollector, TestInfo, Yul +from ethereum_test_tools.common.types import AllocMode, contract_address_iterator from ethereum_test_tools.utility.versioning import ( generate_github_url, get_current_commit_hash_or_tag, @@ -150,6 +151,31 @@ def pytest_addoption(parser): "The --html flag can be used to specify a different path." ), ) + test_group.addoption( + "--strict-alloc", + action="store_true", + dest="strict_alloc", + default=False, + help=("[DEBUG ONLY] Disallows deploying a contract in a predefined address."), + ) + test_group.addoption( + "--ca-start", + "--contract-address-start", + action="store", + dest="test_contract_start_address", + default=None, + type=str, + help="The starting address from which tests will deploy contracts.", + ) + test_group.addoption( + "--ca-incr", + "--contract-address-increment", + action="store", + dest="test_contract_address_increments", + default=None, + type=str, + help="The address increment value to each deployed contract by a test.", + ) debug_group = parser.getgroup("debug", "Arguments defining debug behavior") debug_group.addoption( @@ -381,6 +407,27 @@ def t8n(request, evm_bin: Path) -> Generator[TransitionTool, None, None]: t8n.shutdown() +@pytest.fixture(autouse=True) +def pre(request) -> Alloc: + """ + Returns the default pre allocation for all tests (Empty alloc). + """ + pre = Alloc() + if request.config.getoption("strict_alloc"): + pre._alloc_mode = AllocMode.STRICT + test_contract_start_address = request.config.getoption("test_contract_start_address") + test_contract_address_increments = request.config.getoption("test_contract_address_increments") + if test_contract_start_address is not None or test_contract_address_increments is not None: + kw_args = {} + if test_contract_start_address is not None: + kw_args["start_address"] = int(test_contract_start_address, 0) + if test_contract_address_increments is not None: + kw_args["increment"] = int(test_contract_address_increments, 0) + pre._contract_address_iterator = contract_address_iterator(**kw_args) + + return pre + + @pytest.fixture(scope="session") def do_fixture_verification(request, t8n) -> bool: """ @@ -639,6 +686,8 @@ def base_test_parametrizer_func( class BaseTestWrapper(cls): def __init__(self, *args, **kwargs): kwargs["t8n_dump_dir"] = dump_dir_parameter_level + if "pre" not in kwargs: + kwargs["pre"] = request.getfixturevalue("pre") super(BaseTestWrapper, self).__init__(*args, **kwargs) fixture = self.generate( t8n=t8n, diff --git a/tests/berlin/eip2930_access_list/test_acl.py b/tests/berlin/eip2930_access_list/test_acl.py index b69a5ada7e..c5e2f0511c 100644 --- a/tests/berlin/eip2930_access_list/test_acl.py +++ b/tests/berlin/eip2930_access_list/test_acl.py @@ -4,7 +4,7 @@ import pytest -from ethereum_test_tools import AccessList, Account, Environment +from ethereum_test_tools import AccessList, Account, Alloc, Environment from ethereum_test_tools import Opcodes as Op from ethereum_test_tools import StateTestFiller, Transaction @@ -13,29 +13,22 @@ @pytest.mark.valid_from("Berlin") -def test_access_list(state_test: StateTestFiller): +def test_access_list(state_test: StateTestFiller, pre: Alloc): """ Test type 1 transaction. """ env = Environment() - pre = { - "0x000000000000000000000000000000000000aaaa": Account( - balance=0x03, - code=Op.PC + Op.SLOAD + Op.POP + Op.PC + Op.SLOAD, - nonce=1, - ), - "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": Account( - balance=0x300000, - nonce=0, - ), - } + contract_address = pre.deploy_contract( + Op.PC + Op.SLOAD + Op.POP + Op.PC + Op.SLOAD, + balance=0x03, + ) + sender = pre.fund_eoa(0x300000) tx = Transaction( ty=1, chain_id=0x01, - nonce=0, - to="0x000000000000000000000000000000000000aaaa", + to=contract_address, value=1, gas_limit=323328, gas_price=7, @@ -49,15 +42,16 @@ def test_access_list(state_test: StateTestFiller): ], secret_key="0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", protected=True, + sender=sender, ) post = { - "0x000000000000000000000000000000000000aaaa": Account( + contract_address: Account( code="0x5854505854", balance=4, nonce=1, ), - "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": Account( + sender: Account( balance=0x2CD931, nonce=1, ), diff --git a/tests/byzantium/eip198_modexp_precompile/test_modexp.py b/tests/byzantium/eip198_modexp_precompile/test_modexp.py index 239d9139f0..d9fa787bcb 100644 --- a/tests/byzantium/eip198_modexp_precompile/test_modexp.py +++ b/tests/byzantium/eip198_modexp_precompile/test_modexp.py @@ -9,10 +9,9 @@ from ethereum_test_tools import ( Account, - Address, + Alloc, Environment, StateTestFiller, - TestAddress, TestParameterGroup, Transaction, compute_create_address, @@ -206,65 +205,63 @@ class ExpectedOutput(TestParameterGroup): ], ids=lambda param: param.__repr__(), # only required to remove parameter names (input/output) ) -def test_modexp(state_test: StateTestFiller, input: ModExpInput, output: ExpectedOutput): +def test_modexp( + state_test: StateTestFiller, input: ModExpInput, output: ExpectedOutput, pre: Alloc +): """ Test the MODEXP precompile """ env = Environment() - pre = {TestAddress: Account(balance=1000000000000000000000)} + sender = pre.fund_eoa() - account = Address(0x100) - - pre[account] = Account( - code=( - # Store all CALLDATA into memory (offset 0) - Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE()) - # Store the returned CALL status (success = 1, fail = 0) into slot 0: - + Op.SSTORE( - 0, - # Setup stack to CALL into ModExp with the CALLDATA and CALL into it (+ pop value) - Op.CALL(Op.GAS(), 0x05, 0, 0, Op.CALLDATASIZE(), 0, 0), - ) - # Store contract deployment code to deploy the returned data from ModExp as - # contract code (16 bytes) - + Op.MSTORE( - 0, + account = pre.deploy_contract( + # Store all CALLDATA into memory (offset 0) + Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE()) + # Store the returned CALL status (success = 1, fail = 0) into slot 0: + + Op.SSTORE( + 0, + # Setup stack to CALL into ModExp with the CALLDATA and CALL into it (+ pop value) + Op.CALL(Op.GAS(), 0x05, 0, 0, Op.CALLDATASIZE(), 0, 0), + ) + # Store contract deployment code to deploy the returned data from ModExp as + # contract code (16 bytes) + + Op.MSTORE( + 0, + ( ( - ( - # Need to `ljust` this PUSH32 in order to ensure the code starts - # in memory at offset 0 (memory right-aligns stack items which are not - # 32 bytes) - Op.PUSH32( - ( - Op.CODECOPY(0, 16, Op.SUB(Op.CODESIZE(), 16)) - + Op.RETURN(0, Op.SUB(Op.CODESIZE, 16)) - ).ljust(32, bytes(1)) - ) + # Need to `ljust` this PUSH32 in order to ensure the code starts + # in memory at offset 0 (memory right-aligns stack items which are not + # 32 bytes) + Op.PUSH32( + ( + Op.CODECOPY(0, 16, Op.SUB(Op.CODESIZE(), 16)) + + Op.RETURN(0, Op.SUB(Op.CODESIZE, 16)) + ).ljust(32, bytes(1)) ) - ), - ) - # RETURNDATACOPY the returned data from ModExp into memory (offset 16 bytes) - + Op.RETURNDATACOPY(16, 0, Op.RETURNDATASIZE()) - # CREATE contract with the deployment code + the returned data from ModExp - + Op.CREATE(0, 0, Op.ADD(16, Op.RETURNDATASIZE())) - # STOP (handy for tracing) - + Op.STOP() + ) + ), ) + # RETURNDATACOPY the returned data from ModExp into memory (offset 16 bytes) + + Op.RETURNDATACOPY(16, 0, Op.RETURNDATASIZE()) + # CREATE contract with the deployment code + the returned data from ModExp + + Op.CREATE(0, 0, Op.ADD(16, Op.RETURNDATASIZE())) + # STOP (handy for tracing) + + Op.STOP(), ) tx = Transaction( ty=0x0, - nonce=0, to=account, data=input.create_modexp_tx_data(), gas_limit=500000, gas_price=10, protected=True, + sender=sender, ) post = {} if output.call_return_code != "0x00": - contract_address = compute_create_address(account, tx.nonce) + contract_address = compute_create_address(account, 1) post[contract_address] = Account(code=output.returned_data) post[account] = Account(storage={0: output.call_return_code}) diff --git a/tests/constantinople/create2/test_recreate.py b/tests/constantinople/create2/test_recreate.py index 15c166f860..1aaaf940f3 100644 --- a/tests/constantinople/create2/test_recreate.py +++ b/tests/constantinople/create2/test_recreate.py @@ -5,16 +5,19 @@ import pytest from ethereum_test_forks import Fork -from ethereum_test_tools import Account, Block, BlockchainTestFiller, Environment, Initcode +from ethereum_test_tools import Account, Alloc, Block, BlockchainTestFiller, Environment, Initcode from ethereum_test_tools import Opcodes as Op -from ethereum_test_tools import TestAddress, Transaction, Yul, compute_create2_address +from ethereum_test_tools import Transaction, Yul, compute_create2_address @pytest.mark.parametrize("recreate_on_separate_block", [True, False]) @pytest.mark.valid_from("Constantinople") @pytest.mark.valid_until("Shanghai") def test_recreate( - blockchain_test: BlockchainTestFiller, fork: Fork, recreate_on_separate_block: bool + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + recreate_on_separate_block: bool, ): """ Test that the storage is cleared when a contract is first destructed then re-created using @@ -22,18 +25,11 @@ def test_recreate( """ env = Environment() - creator_address = 0x100 creator_contract_code = Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + Op.CREATE2( 0, 0, Op.CALLDATASIZE, 0 ) - - pre = { - TestAddress: Account(balance=1000000000000000000000), - creator_address: Account( - code=creator_contract_code, - nonce=1, - ), - } + creator_address = pre.deploy_contract(creator_contract_code) + sender = pre.fund_eoa() deploy_code = Yul( """ @@ -53,10 +49,10 @@ def test_recreate( initcode = Initcode(deploy_code=deploy_code) create_tx = Transaction( - nonce=0, gas_limit=100000000, to=creator_address, data=initcode, + sender=sender, ) created_contract_address = compute_create2_address( @@ -64,34 +60,34 @@ def test_recreate( ) set_storage_tx = Transaction( - nonce=1, gas_limit=100000000, to=created_contract_address, value=1, + sender=sender, ) blocks = [Block(txs=[create_tx, set_storage_tx])] destruct_tx = Transaction( - nonce=2, gas_limit=100000000, to=created_contract_address, value=0, + sender=sender, ) balance = 1 send_funds_tx = Transaction( - nonce=3, gas_limit=100000000, to=created_contract_address, value=balance, + sender=sender, ) re_create_tx = Transaction( - nonce=4, gas_limit=100000000, to=creator_address, data=initcode, + sender=sender, ) if recreate_on_separate_block: diff --git a/tests/frontier/opcodes/test_call_and_callcode_gas_calculation.py b/tests/frontier/opcodes/test_call_and_callcode_gas_calculation.py index cffb53a624..96e5b1de42 100644 --- a/tests/frontier/opcodes/test_call_and_callcode_gas_calculation.py +++ b/tests/frontier/opcodes/test_call_and_callcode_gas_calculation.py @@ -23,17 +23,17 @@ verify whether the provided gas was sufficient or insufficient. """ -from dataclasses import dataclass from typing import Dict import pytest from ethereum_test_tools import ( + EOA, Account, Address, + Alloc, Environment, StateTestFiller, - TestAddress, Transaction, ) from ethereum_test_tools.vm.opcode import Opcodes as Op @@ -61,17 +61,37 @@ CALLCODE_SUFFICIENT_GAS = CALLCODE_GAS + CALLEE_INIT_STACK_GAS -@dataclass(frozen=True) -class Contract: - """Contract accounts used in the test.""" +@pytest.fixture +def callee_code(callee_opcode: Op) -> bytes: + """ + Code called by the caller contract: + PUSH1 0x00 * 4 + PUSH1 0x01 <- for positive value transfer + PUSH2 Contract.nonexistent + GAS <- value doesn't matter + CALL/CALLCODE + """ + return callee_opcode(Op.GAS(), 0xFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, 1, 0, 0, 0, 0) - caller: int = 0x0A - callee: int = 0x0B - nonexistent: int = 0x0C + +@pytest.fixture +def sender(pre: Alloc) -> EOA: + """ + Sender for all transactions. + """ + return pre.fund_eoa(0x0BA1A9CE) + + +@pytest.fixture +def callee_address(pre: Alloc, callee_code: bytes) -> Address: + """ + Address of the callee. + """ + return pre.deploy_contract(callee_code, balance=0x03) @pytest.fixture -def caller_code(caller_gas_limit: int) -> bytes: +def caller_code(caller_gas_limit: int, callee_address: Address) -> bytes: """ Code to CALL the callee contract: PUSH1 0x00 * 5 @@ -81,58 +101,40 @@ def caller_code(caller_gas_limit: int) -> bytes: PUSH1 0x00 SSTORE """ - return Op.SSTORE(0, Op.CALL(caller_gas_limit, Contract.callee, 0, 0, 0, 0, 0)) + return Op.SSTORE(0, Op.CALL(caller_gas_limit, callee_address, 0, 0, 0, 0, 0)) @pytest.fixture -def callee_code(callee_opcode: Op) -> bytes: +def caller_address(pre: Alloc, caller_code: bytes) -> Address: """ - Code called by the caller contract: - PUSH1 0x00 * 4 - PUSH1 0x01 <- for positive value transfer - PUSH2 Contract.nonexistent - GAS <- value doesn't matter - CALL/CALLCODE + Code to CALL the callee contract: + PUSH1 0x00 * 5 + PUSH2 Contract.callee + PUSH2 caller_gas <- gas limit set for CALL to callee contract + CALL + PUSH1 0x00 + SSTORE """ - return callee_opcode(Op.GAS(), Contract.nonexistent, 1, 0, 0, 0, 0) + return pre.deploy_contract(caller_code, balance=0x03) @pytest.fixture -def caller_tx() -> Transaction: +def caller_tx(sender: EOA, caller_address: Address) -> Transaction: """Transaction that performs the call to the caller contract.""" return Transaction( chain_id=0x01, - nonce=0, - to=Address(Contract.caller), + to=caller_address, value=1, gas_limit=500000, gas_price=7, + sender=sender, ) @pytest.fixture -def pre(caller_code: bytes, callee_code: bytes) -> Dict[Address, Account]: # noqa: D103 - return { - Address(Contract.caller): Account( - balance=0x03, - code=caller_code, - nonce=1, - ), - Address(Contract.callee): Account( - balance=0x03, - code=callee_code, - nonce=1, - ), - TestAddress: Account( - balance=0x0BA1A9CE, - ), - } - - -@pytest.fixture -def post(is_sufficient_gas: bool) -> Dict[Address, Account]: # noqa: D103 +def post(caller_address: Address, is_sufficient_gas: bool) -> Dict[Address, Account]: # noqa: D103 return { - Address(Contract.caller): Account(storage={0x00: 0x01 if is_sufficient_gas else 0x00}), + caller_address: Account(storage={0x00: 0x01 if is_sufficient_gas else 0x00}), } @@ -149,7 +151,7 @@ def post(is_sufficient_gas: bool) -> Dict[Address, Account]: # noqa: D103 @pytest.mark.valid_until("Shanghai") def test_value_transfer_gas_calculation( state_test: StateTestFiller, - pre: Dict[str, Account], + pre: Alloc, caller_tx: Transaction, post: Dict[str, Account], ): diff --git a/tests/frontier/opcodes/test_dup.py b/tests/frontier/opcodes/test_dup.py index 00894dd284..c67a088d94 100644 --- a/tests/frontier/opcodes/test_dup.py +++ b/tests/frontier/opcodes/test_dup.py @@ -6,9 +6,9 @@ import pytest from ethereum_test_forks import Frontier, Homestead -from ethereum_test_tools import Account, Address, Environment +from ethereum_test_tools import Account, Alloc, Environment from ethereum_test_tools import Opcodes as Op -from ethereum_test_tools import StateTestFiller, Storage, TestAddress, Transaction +from ethereum_test_tools import StateTestFiller, Storage, Transaction @pytest.mark.parametrize( @@ -37,6 +37,7 @@ def test_dup( state_test: StateTestFiller, fork: str, dup_opcode: Op, + pre: Alloc, ): """ Test the DUP1-DUP16 opcodes. @@ -47,11 +48,9 @@ def test_dup( by Ori Pomerantz. """ # noqa: E501 env = Environment() - pre = {TestAddress: Account(balance=1000000000000000000000)} + sender = pre.fund_eoa() post = {} - account = Address(0x100) - # Push 0x00 - 0x10 onto the stack account_code = b"".join([Op.PUSH1(i) for i in range(0x11)]) @@ -61,7 +60,7 @@ def test_dup( # Save each stack value into different keys in storage account_code += b"".join([Op.PUSH1(i) + Op.SSTORE for i in range(0x11)]) - pre[account] = Account(code=account_code) + account = pre.deploy_contract(account_code) tx = Transaction( ty=0x0, @@ -71,6 +70,7 @@ def test_dup( gas_price=10, protected=False if fork in [Frontier, Homestead] else True, data="", + sender=sender, ) """ diff --git a/tests/frontier/opcodes/test_selfdestruct.py b/tests/frontier/opcodes/test_selfdestruct.py index b114bbfd9b..a4b2dc950b 100644 --- a/tests/frontier/opcodes/test_selfdestruct.py +++ b/tests/frontier/opcodes/test_selfdestruct.py @@ -4,58 +4,54 @@ import pytest -from ethereum_test_tools import Account, Block, BlockchainTestFiller, Environment, Initcode +from ethereum_test_tools import Account, Alloc, Block, BlockchainTestFiller, Environment, Initcode from ethereum_test_tools import Opcodes as Op -from ethereum_test_tools import TestAddress, Transaction, compute_create_address +from ethereum_test_tools import Transaction @pytest.mark.valid_from("Frontier") @pytest.mark.valid_until("Homestead") -def test_double_kill(blockchain_test: BlockchainTestFiller): +def test_double_kill(blockchain_test: BlockchainTestFiller, pre: Alloc): """ Test that when two transactions attempt to destruct a contract, the second transaction actually resurrects the contract as an empty account (prior to Spurious Dragon). """ env = Environment() - pre = { - TestAddress: Account(balance=1000000000000000000000), - } + sender = pre.fund_eoa() deploy_code = Op.SELFDESTRUCT(Op.ADDRESS) initcode = Initcode(deploy_code=deploy_code) create_tx = Transaction( - nonce=0, gas_limit=100000000, protected=False, to=None, data=initcode, + sender=sender, ) - created_contract_address = compute_create_address(address=TestAddress, nonce=0) - block_1 = Block(txs=[create_tx]) first_kill = Transaction( - nonce=1, gas_limit=100000000, protected=False, - to=created_contract_address, + to=create_tx.created_contract, + sender=sender, ) second_kill = Transaction( - nonce=2, gas_limit=100000000, protected=False, - to=created_contract_address, + to=create_tx.created_contract, + sender=sender, ) block_2 = Block(txs=[first_kill, second_kill]) post = { - created_contract_address: Account( + create_tx.created_contract: Account( nonce=0, balance=0, code=b"", diff --git a/tests/homestead/yul/test_yul_example.py b/tests/homestead/yul/test_yul_example.py index 1e6fad07a2..f57b926743 100644 --- a/tests/homestead/yul/test_yul_example.py +++ b/tests/homestead/yul/test_yul_example.py @@ -7,26 +7,24 @@ from ethereum_test_forks import Fork, Frontier, Homestead from ethereum_test_tools import ( Account, + Alloc, Environment, StateTestFiller, - TestAddress, Transaction, YulCompiler, ) @pytest.mark.valid_from("Homestead") -def test_yul(state_test: StateTestFiller, yul: YulCompiler, fork: Fork): +def test_yul(state_test: StateTestFiller, pre: Alloc, yul: YulCompiler, fork: Fork): """ Test YUL compiled bytecode. """ env = Environment() - pre = { - "0x1000000000000000000000000000000000000000": Account( - balance=0x0BA1A9CE0BA1A9CE, - code=yul( - """ + contract_address = pre.deploy_contract( + code=yul( + """ { function f(a, b) -> c { c := add(a, b) @@ -36,23 +34,23 @@ def test_yul(state_test: StateTestFiller, yul: YulCompiler, fork: Fork): return(0, 32) } """ - ), ), - TestAddress: Account(balance=0x0BA1A9CE0BA1A9CE), - } + balance=0x0BA1A9CE0BA1A9CE, + ) + sender = pre.fund_eoa(amount=0x0BA1A9CE0BA1A9CE) tx = Transaction( ty=0x0, chain_id=0x01, - nonce=0, - to="0x1000000000000000000000000000000000000000", + sender=sender, + to=contract_address, gas_limit=500000, gas_price=10, protected=False if fork in [Frontier, Homestead] else True, ) post = { - "0x1000000000000000000000000000000000000000": Account( + contract_address: Account( storage={ 0x00: 0x03, }, diff --git a/tests/istanbul/eip1344_chainid/test_chainid.py b/tests/istanbul/eip1344_chainid/test_chainid.py index e9c3fc8a77..35bdecd2ea 100644 --- a/tests/istanbul/eip1344_chainid/test_chainid.py +++ b/tests/istanbul/eip1344_chainid/test_chainid.py @@ -5,14 +5,7 @@ import pytest -from ethereum_test_tools import ( - Account, - Address, - Environment, - StateTestFiller, - TestAddress, - Transaction, -) +from ethereum_test_tools import Account, Alloc, Environment, StateTestFiller, Transaction from ethereum_test_tools.vm.opcode import Opcodes as Op REFERENCE_SPEC_GIT_PATH = "EIPS/eip-1344.md" @@ -20,7 +13,7 @@ @pytest.mark.valid_from("Istanbul") -def test_chainid(state_test: StateTestFiller): +def test_chainid(state_test: StateTestFiller, pre: Alloc): """ Test CHAINID opcode. """ @@ -32,22 +25,20 @@ def test_chainid(state_test: StateTestFiller): timestamp=1000, ) - pre = { - Address(0x100): Account(code=Op.SSTORE(1, Op.CHAINID) + Op.STOP), - TestAddress: Account(balance=1000000000000000000000), - } + contract_address = pre.deploy_contract(Op.SSTORE(1, Op.CHAINID) + Op.STOP) + sender = pre.fund_eoa() tx = Transaction( ty=0x0, chain_id=0x01, - nonce=0, - to=Address(0x100), + to=contract_address, gas_limit=100000000, gas_price=10, + sender=sender, ) post = { - Address(0x100): Account(code="0x4660015500", storage={"0x01": "0x01"}), + contract_address: Account(storage={"0x01": "0x01"}), } state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/prague/eip2537_bls_12_381_precompiles/conftest.py b/tests/prague/eip2537_bls_12_381_precompiles/conftest.py index 454ca4baa2..1b80e21af8 100644 --- a/tests/prague/eip2537_bls_12_381_precompiles/conftest.py +++ b/tests/prague/eip2537_bls_12_381_precompiles/conftest.py @@ -6,7 +6,7 @@ import pytest from ethereum.crypto.hash import keccak256 -from ethereum_test_tools import Storage, TestAddress, Transaction +from ethereum_test_tools import EOA, Address, Alloc, Storage, Transaction from ethereum_test_tools.vm import Opcodes as Op from .spec import GAS_CALCULATION_FUNCTION_MAP @@ -128,29 +128,19 @@ def call_contract_code( @pytest.fixture -def call_contract_address() -> int: +def call_contract_address(pre: Alloc, call_contract_code: bytes) -> Address: """Address where the test contract will be deployed.""" - return 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + return pre.deploy_contract(call_contract_code) @pytest.fixture -def pre(call_contract_address: int, call_contract_code: bytes): - """Pre-allocation for every test.""" - return { - call_contract_address: { - "balance": 0, - "nonce": 1, - "code": call_contract_code, - }, - TestAddress: { - "balance": 1_000_000_000_000_000, - "nonce": 0, - }, - } +def sender(pre: Alloc) -> EOA: + """Sender of the transaction.""" + return pre.fund_eoa(1_000_000_000_000_000) @pytest.fixture -def post(call_contract_address: int, call_contract_post_storage: Storage): +def post(call_contract_address: Address, call_contract_post_storage: Storage): """Test expected post outcome.""" return { call_contract_address: { @@ -168,10 +158,16 @@ def tx_gas_limit(precompile_gas: int) -> int: @pytest.fixture -def tx(input: bytes, tx_gas_limit: int, call_contract_address: int) -> Transaction: +def tx( + input: bytes, + tx_gas_limit: int, + call_contract_address: Address, + sender: EOA, +) -> Transaction: """Transaction for the test.""" return Transaction( gas_limit=tx_gas_limit, input=input, to=call_contract_address, + sender=sender, ) diff --git a/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1add.py b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1add.py index 7d03436eb8..e20eaba883 100644 --- a/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1add.py +++ b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1add.py @@ -5,7 +5,7 @@ import pytest -from ethereum_test_tools import Environment +from ethereum_test_tools import Alloc, Environment from ethereum_test_tools import Opcodes as Op from ethereum_test_tools import StateTestFiller, Transaction @@ -44,7 +44,7 @@ ) def test_valid( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): @@ -152,7 +152,7 @@ def test_valid( @pytest.mark.parametrize("expected_output", [Spec.INVALID], ids=[""]) def test_invalid( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): @@ -186,7 +186,7 @@ def test_invalid( ) def test_gas( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): @@ -221,7 +221,7 @@ def test_gas( ) def test_call_types( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): diff --git a/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1msm.py b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1msm.py index 94f53e45dd..35d2b9c5db 100644 --- a/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1msm.py +++ b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1msm.py @@ -5,7 +5,7 @@ import pytest -from ethereum_test_tools import Environment +from ethereum_test_tools import Alloc, Environment from ethereum_test_tools import Opcodes as Op from ethereum_test_tools import StateTestFiller, Transaction @@ -39,7 +39,7 @@ ) def test_valid( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): @@ -103,7 +103,7 @@ def test_valid( @pytest.mark.parametrize("expected_output", [Spec.INVALID], ids=[""]) def test_invalid( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): @@ -138,7 +138,7 @@ def test_invalid( ) def test_call_types( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): diff --git a/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1mul.py b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1mul.py index 9b2c24232e..7744137acd 100644 --- a/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1mul.py +++ b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1mul.py @@ -5,7 +5,7 @@ import pytest -from ethereum_test_tools import Environment +from ethereum_test_tools import Alloc, Environment from ethereum_test_tools import Opcodes as Op from ethereum_test_tools import StateTestFiller, Transaction @@ -72,7 +72,7 @@ ) def test_valid( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): @@ -156,7 +156,7 @@ def test_valid( @pytest.mark.parametrize("expected_output", [Spec.INVALID], ids=[""]) def test_invalid( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): @@ -190,7 +190,7 @@ def test_invalid( ) def test_gas( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): @@ -225,7 +225,7 @@ def test_gas( ) def test_call_types( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): diff --git a/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g2add.py b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g2add.py index adcd905427..90931031c3 100644 --- a/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g2add.py +++ b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g2add.py @@ -5,7 +5,7 @@ import pytest -from ethereum_test_tools import Environment +from ethereum_test_tools import Alloc, Environment from ethereum_test_tools import Opcodes as Op from ethereum_test_tools import StateTestFiller, Transaction @@ -34,7 +34,7 @@ ) def test_valid( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): @@ -158,7 +158,7 @@ def test_valid( @pytest.mark.parametrize("expected_output", [Spec.INVALID], ids=[""]) def test_invalid( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): @@ -192,7 +192,7 @@ def test_invalid( ) def test_gas( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): @@ -227,7 +227,7 @@ def test_gas( ) def test_call_types( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): diff --git a/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g2msm.py b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g2msm.py index e3236c7787..ea1eca73c7 100644 --- a/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g2msm.py +++ b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g2msm.py @@ -5,7 +5,7 @@ import pytest -from ethereum_test_tools import Environment +from ethereum_test_tools import Alloc, Environment from ethereum_test_tools import Opcodes as Op from ethereum_test_tools import StateTestFiller, Transaction @@ -24,7 +24,7 @@ @pytest.mark.parametrize("input,expected_output", vectors_from_file("multiexp_G2_bls.json")) def test_valid( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): @@ -93,7 +93,7 @@ def test_valid( @pytest.mark.parametrize("expected_output", [Spec.INVALID], ids=[""]) def test_invalid( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): @@ -128,7 +128,7 @@ def test_invalid( ) def test_call_types( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): diff --git a/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g2mul.py b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g2mul.py index 118a4449fe..f1ebe2873d 100644 --- a/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g2mul.py +++ b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g2mul.py @@ -5,7 +5,7 @@ import pytest -from ethereum_test_tools import Environment +from ethereum_test_tools import Alloc, Environment from ethereum_test_tools import Opcodes as Op from ethereum_test_tools import StateTestFiller, Transaction @@ -83,7 +83,7 @@ ) def test_valid( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): @@ -179,7 +179,7 @@ def test_valid( @pytest.mark.parametrize("expected_output", [Spec.INVALID], ids=[""]) def test_invalid( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): @@ -213,7 +213,7 @@ def test_invalid( ) def test_gas( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): @@ -248,7 +248,7 @@ def test_gas( ) def test_call_types( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): diff --git a/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_map_fp2_to_g2.py b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_map_fp2_to_g2.py index 385f3ad38f..cb12b58b28 100644 --- a/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_map_fp2_to_g2.py +++ b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_map_fp2_to_g2.py @@ -6,7 +6,7 @@ import pytest -from ethereum_test_tools import Environment +from ethereum_test_tools import Alloc, Environment from ethereum_test_tools import Opcodes as Op from ethereum_test_tools import StateTestFiller, Transaction @@ -60,7 +60,7 @@ ) def test_valid( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): @@ -93,7 +93,7 @@ def test_valid( @pytest.mark.parametrize("expected_output", [Spec.INVALID], ids=[""]) def test_invalid( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): @@ -127,7 +127,7 @@ def test_invalid( ) def test_gas( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): @@ -162,7 +162,7 @@ def test_gas( ) def test_call_types( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): diff --git a/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_map_fp_to_g1.py b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_map_fp_to_g1.py index 71247fcb49..165b1a551a 100644 --- a/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_map_fp_to_g1.py +++ b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_map_fp_to_g1.py @@ -6,7 +6,7 @@ import pytest -from ethereum_test_tools import Environment +from ethereum_test_tools import Alloc, Environment from ethereum_test_tools import Opcodes as Op from ethereum_test_tools import StateTestFiller, Transaction @@ -48,7 +48,7 @@ ) def test_valid( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): @@ -79,7 +79,7 @@ def test_valid( @pytest.mark.parametrize("expected_output", [Spec.INVALID], ids=[""]) def test_invalid( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): @@ -113,7 +113,7 @@ def test_invalid( ) def test_gas( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): @@ -148,7 +148,7 @@ def test_gas( ) def test_call_types( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): diff --git a/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_pairing.py b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_pairing.py index 729f685525..3c7499e196 100644 --- a/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_pairing.py +++ b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_pairing.py @@ -5,7 +5,7 @@ import pytest -from ethereum_test_tools import Environment +from ethereum_test_tools import Alloc, Environment from ethereum_test_tools import Opcodes as Op from ethereum_test_tools import StateTestFiller, Transaction @@ -39,7 +39,7 @@ ) def test_valid( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): @@ -108,7 +108,7 @@ def test_valid( @pytest.mark.parametrize("expected_output", [Spec.INVALID], ids=[""]) def test_invalid( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): @@ -143,7 +143,7 @@ def test_invalid( ) def test_call_types( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): diff --git a/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_precompiles_before_fork.py b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_precompiles_before_fork.py index d5ae6bb96d..cf0d6d7662 100644 --- a/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_precompiles_before_fork.py +++ b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_precompiles_before_fork.py @@ -6,7 +6,7 @@ import pytest -from ethereum_test_tools import Environment, StateTestFiller, Transaction +from ethereum_test_tools import Alloc, Environment, StateTestFiller, Transaction from .spec import FP, FP2, Scalar, Spec, ref_spec_2537 @@ -69,7 +69,7 @@ @pytest.mark.parametrize("expected_output,call_succeeds", [pytest.param(b"", True, id="")]) def test_precompile_before_fork( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): diff --git a/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_variable_length_input_contracts.py b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_variable_length_input_contracts.py index 159cb083ca..444a0e9f52 100644 --- a/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_variable_length_input_contracts.py +++ b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_variable_length_input_contracts.py @@ -7,7 +7,7 @@ import pytest -from ethereum_test_tools import Environment +from ethereum_test_tools import Alloc, Environment from ethereum_test_tools import Opcodes as Op from ethereum_test_tools import StateTestFiller, Storage, Transaction @@ -120,7 +120,7 @@ def call_contract_code( @pytest.mark.parametrize("precompile_address", [Spec.G1MSM]) def test_valid_gas_g1msm( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): @@ -161,7 +161,7 @@ def test_valid_gas_g1msm( @pytest.mark.parametrize("precompile_address", [Spec.G1MSM]) def test_invalid_gas_g1msm( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): @@ -204,7 +204,7 @@ def test_invalid_gas_g1msm( @pytest.mark.parametrize("precompile_address", [Spec.G1MSM]) def test_invalid_length_g1msm( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): @@ -245,7 +245,7 @@ def test_invalid_length_g1msm( @pytest.mark.parametrize("precompile_address", [Spec.G2MSM]) def test_valid_gas_g2msm( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): @@ -286,7 +286,7 @@ def test_valid_gas_g2msm( @pytest.mark.parametrize("precompile_address", [Spec.G2MSM]) def test_invalid_gas_g2msm( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): @@ -329,7 +329,7 @@ def test_invalid_gas_g2msm( @pytest.mark.parametrize("precompile_address", [Spec.G2MSM]) def test_invalid_length_g2msm( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): @@ -367,7 +367,7 @@ def test_invalid_length_g2msm( @pytest.mark.parametrize("precompile_address", [Spec.PAIRING]) def test_valid_gas_pairing( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): @@ -405,7 +405,7 @@ def test_valid_gas_pairing( @pytest.mark.parametrize("precompile_address", [Spec.PAIRING]) def test_invalid_gas_pairing( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): @@ -448,7 +448,7 @@ def test_invalid_gas_pairing( @pytest.mark.parametrize("precompile_address", [Spec.PAIRING]) def test_invalid_length_pairing( state_test: StateTestFiller, - pre: dict, + pre: Alloc, post: dict, tx: Transaction, ): diff --git a/tests/prague/eip2935_historical_block_hashes_from_state/test_block_hashes.py b/tests/prague/eip2935_historical_block_hashes_from_state/test_block_hashes.py index b558b83b17..f633b17e78 100644 --- a/tests/prague/eip2935_historical_block_hashes_from_state/test_block_hashes.py +++ b/tests/prague/eip2935_historical_block_hashes_from_state/test_block_hashes.py @@ -3,14 +3,13 @@ Test [EIP-2935: Serve historical block hashes from state](https://eips.ethereum.org/EIPS/eip-2935) """ # noqa: E501 -from itertools import count from typing import Dict, List import pytest -from ethereum_test_tools import Account, Address, Block, BlockchainTestFiller, Environment +from ethereum_test_tools import Account, Address, Alloc, Block, BlockchainTestFiller, Environment from ethereum_test_tools import Opcodes as Op -from ethereum_test_tools import Storage, TestAddress, Transaction +from ethereum_test_tools import Storage, Transaction from .spec import Spec, ref_spec_2935 @@ -86,6 +85,7 @@ def generate_block_check_code( @pytest.mark.valid_at_transition_to("Prague") def test_block_hashes_history_at_transition( blockchain_test: BlockchainTestFiller, + pre: Alloc, blocks_before_fork: int, ): """ @@ -96,11 +96,9 @@ def test_block_hashes_history_at_transition( blocks: List[Block] = [] assert blocks_before_fork >= 1 and blocks_before_fork < FORK_TIMESTAMP - pre = {TestAddress: Account(balance=10_000_000_000)} + sender = pre.fund_eoa(10_000_000_000) post: Dict[Address, Account] = {} - tx_nonce = count(0) - current_code_address = 0x10000 for i in range(1, blocks_before_fork): txs: List[Transaction] = [] if i == blocks_before_fork - 1: @@ -134,16 +132,15 @@ def test_block_hashes_history_at_transition( storage=storage, ) + code_address = pre.deploy_contract(code) txs.append( Transaction( - to=current_code_address, + to=code_address, gas_limit=10_000_000, - nonce=next(tx_nonce), + sender=sender, ) ) - pre[Address(current_code_address)] = Account(code=code, nonce=1) - post[Address(current_code_address)] = Account(storage=storage) - current_code_address += 0x100 + post[code_address] = Account(storage=storage) blocks.append(Block(timestamp=i, txs=txs)) # Add the fork block @@ -179,16 +176,15 @@ def test_block_hashes_history_at_transition( storage=storage, ) + code_address = pre.deploy_contract(code) txs.append( Transaction( - to=current_code_address, + to=code_address, gas_limit=10_000_000, - nonce=next(tx_nonce), + sender=sender, ) ) - pre[Address(current_code_address)] = Account(code=code, nonce=1) - post[Address(current_code_address)] = Account(storage=storage) - current_code_address += 0x100 + post[code_address] = Account(storage=storage) blocks.append(Block(timestamp=FORK_TIMESTAMP, txs=txs)) diff --git a/tests/prague/eip6110_deposits/conftest.py b/tests/prague/eip6110_deposits/conftest.py index 0aa8f6c4c8..f99c3a3c64 100644 --- a/tests/prague/eip6110_deposits/conftest.py +++ b/tests/prague/eip6110_deposits/conftest.py @@ -1,40 +1,34 @@ """ Fixtures for the EIP-6110 deposit tests. """ -from typing import Dict, List +from typing import List import pytest -from ethereum_test_tools import Account, Address, Block, BlockException, Header, Transaction +from ethereum_test_tools import Alloc, Block, BlockException, Header, Transaction from .helpers import DepositInteractionBase, DepositRequest @pytest.fixture -def pre(requests: List[DepositInteractionBase]) -> Dict[Address, Account]: +def update_pre(pre: Alloc, requests: List[DepositInteractionBase]): """ Initial state of the accounts. Every deposit transaction defines their own pre-state requirements, and this fixture aggregates them all. """ - pre: Dict[Address, Account] = {} for d in requests: d.update_pre(pre) - return pre @pytest.fixture def txs( requests: List[DepositInteractionBase], + update_pre: None, # Fixture is used for its side effects ) -> List[Transaction]: """List of transactions to include in the block.""" - address_nonce: Dict[Address, int] = {} txs = [] for r in requests: - nonce = 0 - if r.sender_account.address in address_nonce: - nonce = address_nonce[r.sender_account.address] - txs.append(r.transaction(nonce)) - address_nonce[r.sender_account.address] = nonce + 1 + txs += r.transactions() return txs diff --git a/tests/prague/eip6110_deposits/helpers.py b/tests/prague/eip6110_deposits/helpers.py index cada0c6fb5..cfe9395d76 100644 --- a/tests/prague/eip6110_deposits/helpers.py +++ b/tests/prague/eip6110_deposits/helpers.py @@ -4,19 +4,13 @@ from dataclasses import dataclass, field from functools import cached_property from hashlib import sha256 as sha256_hashlib -from typing import Callable, ClassVar, Dict, List +from typing import Callable, ClassVar, List -from ethereum_test_tools import Account, Address +from ethereum_test_tools import EOA, Address, Alloc from ethereum_test_tools import DepositRequest as DepositRequestBase from ethereum_test_tools import Hash from ethereum_test_tools import Opcodes as Op -from ethereum_test_tools import ( - TestAddress, - TestAddress2, - TestPrivateKey, - TestPrivateKey2, - Transaction, -) +from ethereum_test_tools import Transaction from .spec import Spec @@ -28,18 +22,6 @@ def sha256(*args: bytes) -> bytes: return sha256_hashlib(b"".join(args)).digest() -@dataclass -class SenderAccount: - """Test sender account descriptor.""" - - address: Address - key: str - - -TestAccount1 = SenderAccount(TestAddress, TestPrivateKey) -TestAccount2 = SenderAccount(TestAddress2, TestPrivateKey2) - - class DepositRequest(DepositRequestBase): """Deposit request descriptor.""" @@ -120,18 +102,20 @@ class DepositInteractionBase: """ Balance of the account that sends the transaction. """ - sender_account: SenderAccount = field( - default_factory=lambda: SenderAccount(TestAddress, TestPrivateKey) - ) + sender_account: EOA | None = None """ Account that sends the transaction. """ + requests: List[DepositRequest] + """ + Deposit request to be included in the block. + """ - def transaction(self, nonce: int) -> Transaction: + def transactions(self) -> List[Transaction]: """Return a transaction for the deposit request.""" raise NotImplementedError - def update_pre(self, base_pre: Dict[Address, Account]): + def update_pre(self, pre: Alloc): """Return the pre-state of the account.""" raise NotImplementedError @@ -144,49 +128,38 @@ def valid_requests(self, current_minimum_fee: int) -> List[DepositRequest]: class DepositTransaction(DepositInteractionBase): """Class used to describe a deposit originated from an externally owned account.""" - request: DepositRequest - """ - Deposit request to be included in the block. - """ - - def transaction(self, nonce: int) -> Transaction: + def transactions(self) -> List[Transaction]: """Return a transaction for the deposit request.""" - return Transaction( - nonce=nonce, - gas_limit=self.request.gas_limit, - gas_price=0x07, - to=self.request.interaction_contract_address, - value=self.request.value, - data=self.request.calldata, - secret_key=self.sender_account.key, - ) + assert self.sender_account is not None, "Sender account not initialized" + return [ + Transaction( + gas_limit=request.gas_limit, + gas_price=0x07, + to=request.interaction_contract_address, + value=request.value, + data=request.calldata, + sender=self.sender_account, + ) + for request in self.requests + ] - def update_pre(self, base_pre: Dict[Address, Account]): + def update_pre(self, pre: Alloc): """Return the pre-state of the account.""" - base_pre.update( - { - self.sender_account.address: Account(balance=self.sender_balance), - } - ) + self.sender_account = pre.fund_eoa(self.sender_balance) def valid_requests(self, current_minimum_fee: int) -> List[DepositRequest]: """Return the list of deposit requests that should be included in the block.""" - return ( - [self.request] - if self.request.valid and self.request.value >= current_minimum_fee - else [] - ) + return [ + request + for request in self.requests + if request.valid and request.value >= current_minimum_fee + ] @dataclass(kw_only=True) class DepositContract(DepositInteractionBase): """Class used to describe a deposit originated from a contract.""" - request: List[DepositRequest] | DepositRequest - """ - Deposit request or list of deposit requests to send from the contract. - """ - tx_gas_limit: int = 1_000_000 """ Gas limit for the transaction. @@ -196,10 +169,14 @@ class DepositContract(DepositInteractionBase): """ Balance of the contract that sends the deposit requests. """ - contract_address: int = 0x200 + contract_address: Address | None = None """ Address of the contract that sends the deposit requests. """ + entry_address: Address | None = None + """ + Address to send the transaction to. + """ call_type: Op = field(default_factory=lambda: Op.CALL) """ @@ -214,13 +191,6 @@ class DepositContract(DepositInteractionBase): Extra code to be included in the contract that sends the deposit requests. """ - @property - def requests(self) -> List[DepositRequest]: - """Return the list of deposit requests.""" - if not isinstance(self.request, List): - return [self.request] - return self.request - @property def contract_code(self) -> bytes: """Contract code used by the relay contract.""" @@ -242,63 +212,42 @@ def contract_code(self) -> bytes: current_offset += len(r.calldata) return code + self.extra_code - def transaction(self, nonce: int) -> Transaction: + def transactions(self) -> List[Transaction]: """Return a transaction for the deposit request.""" - return Transaction( - nonce=nonce, - gas_limit=self.tx_gas_limit, - gas_price=0x07, - to=self.entry_address(), - value=0, - data=b"".join(r.calldata for r in self.requests), - secret_key=self.sender_account.key, - ) - - def entry_address(self) -> Address: - """Return the address of the contract entry point.""" - if self.call_depth == 2: - return Address(self.contract_address) - elif self.call_depth > 2: - return Address(self.contract_address + self.call_depth - 2) - raise ValueError("Invalid call depth") - - def extra_contracts(self) -> Dict[Address, Account]: - """Extra contracts used to simulate call depth.""" - if self.call_depth <= 2: - return {} - return { - Address(self.contract_address + i): Account( - balance=self.contract_balance, - code=Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) - + Op.POP( - Op.CALL( - Op.GAS, - self.contract_address + i - 1, - 0, - 0, - Op.CALLDATASIZE, - 0, - 0, - ) - ), - nonce=1, + return [ + Transaction( + gas_limit=self.tx_gas_limit, + gas_price=0x07, + to=self.entry_address, + value=0, + data=b"".join(r.calldata for r in self.requests), + sender=self.sender_account, ) - for i in range(1, self.call_depth - 1) - } + ] - def update_pre(self, base_pre: Dict[Address, Account]): + def update_pre(self, pre: Alloc): """Return the pre-state of the account.""" - while Address(self.contract_address) in base_pre: - self.contract_address += 0x100 - base_pre.update( - { - self.sender_account.address: Account(balance=self.sender_balance), - Address(self.contract_address): Account( - balance=self.contract_balance, code=self.contract_code, nonce=1 - ), - } + self.sender_account = pre.fund_eoa(self.sender_balance) + self.contract_address = pre.deploy_contract( + code=self.contract_code, balance=self.contract_balance ) - base_pre.update(self.extra_contracts()) + self.entry_address = self.contract_address + if self.call_depth > 2: + for _ in range(1, self.call_depth - 1): + self.entry_address = pre.deploy_contract( + code=Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + + Op.POP( + Op.CALL( + Op.GAS, + self.entry_address, + 0, + 0, + Op.CALLDATASIZE, + 0, + 0, + ) + ), + ) def valid_requests(self, current_minimum_fee: int) -> List[DepositRequest]: """Return the list of deposit requests that should be included in the block.""" diff --git a/tests/prague/eip6110_deposits/test_deposits.py b/tests/prague/eip6110_deposits/test_deposits.py index 8b8f6eac17..341b5c32d9 100644 --- a/tests/prague/eip6110_deposits/test_deposits.py +++ b/tests/prague/eip6110_deposits/test_deposits.py @@ -2,13 +2,12 @@ abstract: Tests [EIP-6110: Supply validator deposits on chain](https://eips.ethereum.org/EIPS/eip-6110) Test [EIP-6110: Supply validator deposits on chain](https://eips.ethereum.org/EIPS/eip-6110). """ # noqa: E501 -from typing import Dict, List +from typing import List import pytest from ethereum_test_tools import ( - Account, - Address, + Alloc, Block, BlockchainTestFiller, BlockException, @@ -17,13 +16,7 @@ ) from ethereum_test_tools import Opcodes as Op -from .helpers import ( - DepositContract, - DepositRequest, - DepositTransaction, - TestAccount1, - TestAccount2, -) +from .helpers import DepositContract, DepositRequest, DepositTransaction from .spec import ref_spec_6110 REFERENCE_SPEC_GIT_PATH = ref_spec_6110.git_path @@ -38,13 +31,15 @@ pytest.param( [ DepositTransaction( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x0, - ), + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ) + ], ), ], id="single_deposit_from_eoa", @@ -52,13 +47,15 @@ pytest.param( [ DepositTransaction( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=120_000_000_000_000_000, - signature=0x03, - index=0x0, - ), + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=120_000_000_000_000_000, + signature=0x03, + index=0x0, + ) + ], sender_balance=120_000_001_000_000_000 * 10**9, ), ], @@ -67,22 +64,22 @@ pytest.param( [ DepositTransaction( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x0, - ), - ), - DepositTransaction( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x1, - ), + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ), + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x1, + ), + ], ), ], id="multiple_deposit_from_same_eoa", @@ -90,39 +87,43 @@ pytest.param( [ DepositTransaction( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=1_000_000_000, - signature=0x03, - index=i, - ), + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=i, + ) + for i in range(200) + ], ) - for i in range(200) ], id="multiple_deposit_from_same_eoa_high_count", ), pytest.param( [ DepositTransaction( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x0, - ), - sender_account=TestAccount1, + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ) + ], ), DepositTransaction( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x1, - ), - sender_account=TestAccount2, + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x1, + ) + ], ), ], id="multiple_deposit_from_different_eoa", @@ -130,22 +131,22 @@ pytest.param( [ DepositTransaction( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=999_999_999, - signature=0x03, - index=0x0, - ), - ), - DepositTransaction( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x0, - ), + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=999_999_999, + signature=0x03, + index=0x0, + ), + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ), + ], ), ], id="multiple_deposit_from_same_eoa_first_reverts", @@ -153,22 +154,22 @@ pytest.param( [ DepositTransaction( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x0, - ), - ), - DepositTransaction( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=999_999_999, - signature=0x03, - index=0x0, - ), + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ), + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=999_999_999, + signature=0x03, + index=0x0, + ), + ], ), ], id="multiple_deposit_from_same_eoa_last_reverts", @@ -176,25 +177,25 @@ pytest.param( [ DepositTransaction( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x0, - # From traces, gas used by the first tx is 82,718 so reduce by one here - gas_limit=0x1431D, - valid=False, - ), - ), - DepositTransaction( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x0, - ), + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + # From traces, gas used by the first tx is 82,718 so reduce by one here + gas_limit=0x1431D, + valid=False, + ), + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ), + ], ), ], id="multiple_deposit_from_same_eoa_first_oog", @@ -202,25 +203,25 @@ pytest.param( [ DepositTransaction( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x0, - ), - ), - DepositTransaction( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x0, - # From traces, gas used by the second tx is 68,594 so reduce by one here - gas_limit=0x10BF1, - valid=False, - ), + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ), + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + # From traces, gas used by the second tx is 68,594, reduce by one here + gas_limit=0x10BF1, + valid=False, + ), + ], ), ], id="multiple_deposit_from_same_eoa_last_oog", @@ -228,15 +229,17 @@ pytest.param( [ DepositTransaction( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x0, - calldata_modifier=lambda _: b"", - valid=False, - ), + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + calldata_modifier=lambda _: b"", + valid=False, + ) + ], ), ], id="send_eth_from_eoa", @@ -244,13 +247,15 @@ pytest.param( [ DepositContract( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x0, - ), + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ) + ], ), ], id="single_deposit_from_contract", @@ -258,7 +263,7 @@ pytest.param( [ DepositContract( - request=[ + requests=[ DepositRequest( pubkey=0x01, withdrawal_credentials=0x02, @@ -281,7 +286,7 @@ pytest.param( [ DepositContract( - request=[ + requests=[ DepositRequest( pubkey=0x01, withdrawal_credentials=0x02, @@ -299,7 +304,7 @@ pytest.param( [ DepositContract( - request=[ + requests=[ DepositRequest( pubkey=0x01, withdrawal_credentials=0x02, @@ -323,7 +328,7 @@ pytest.param( [ DepositContract( - request=[ + requests=[ DepositRequest( pubkey=0x01, withdrawal_credentials=0x02, @@ -347,7 +352,7 @@ pytest.param( [ DepositContract( - request=[ + requests=[ DepositRequest( pubkey=0x01, withdrawal_credentials=0x02, @@ -373,7 +378,7 @@ pytest.param( [ DepositContract( - request=[ + requests=[ DepositRequest( pubkey=0x01, withdrawal_credentials=0x02, @@ -399,7 +404,7 @@ pytest.param( [ DepositContract( - request=[ + requests=[ DepositRequest( pubkey=0x01, withdrawal_credentials=0x02, @@ -425,7 +430,7 @@ pytest.param( [ DepositContract( - request=[ + requests=[ DepositRequest( pubkey=0x01, withdrawal_credentials=0x02, @@ -451,7 +456,7 @@ pytest.param( [ DepositContract( - request=[ + requests=[ DepositRequest( pubkey=0x01, withdrawal_credentials=0x02, @@ -470,7 +475,7 @@ pytest.param( [ DepositContract( - request=[ + requests=[ DepositRequest( pubkey=0x01, withdrawal_credentials=0x02, @@ -481,13 +486,15 @@ ], ), DepositTransaction( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x1, - ), + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x1, + ) + ], ), ], id="single_deposit_from_contract_single_deposit_from_eoa", @@ -495,16 +502,18 @@ pytest.param( [ DepositTransaction( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x0, - ), + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ) + ], ), DepositContract( - request=[ + requests=[ DepositRequest( pubkey=0x01, withdrawal_credentials=0x02, @@ -520,16 +529,18 @@ pytest.param( [ DepositTransaction( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x0, - ), + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ) + ], ), DepositContract( - request=[ + requests=[ DepositRequest( pubkey=0x01, withdrawal_credentials=0x02, @@ -540,13 +551,15 @@ ], ), DepositTransaction( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x2, - ), + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x2, + ) + ], ), ], id="single_deposit_from_contract_between_eoa_deposits", @@ -554,7 +567,7 @@ pytest.param( [ DepositContract( - request=[ + requests=[ DepositRequest( pubkey=0x01, withdrawal_credentials=0x02, @@ -565,16 +578,18 @@ ], ), DepositTransaction( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x1, - ), + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x1, + ) + ], ), DepositContract( - request=[ + requests=[ DepositRequest( pubkey=0x01, withdrawal_credentials=0x02, @@ -590,14 +605,16 @@ pytest.param( [ DepositContract( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x0, - valid=False, - ), + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + valid=False, + ) + ], call_type=Op.DELEGATECALL, ), ], @@ -606,14 +623,16 @@ pytest.param( [ DepositContract( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x0, - valid=False, - ), + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + valid=False, + ) + ], call_type=Op.STATICCALL, ), ], @@ -622,14 +641,16 @@ pytest.param( [ DepositContract( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x0, - valid=False, - ), + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + valid=False, + ) + ], call_type=Op.CALLCODE, ), ], @@ -638,13 +659,15 @@ pytest.param( [ DepositContract( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x0, - ), + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ) + ], call_depth=3, ), ], @@ -653,13 +676,15 @@ pytest.param( [ DepositContract( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x0, - ), + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ) + ], call_depth=1024, tx_gas_limit=2_500_000_000_000, ), @@ -671,7 +696,7 @@ ) def test_deposit( blockchain_test: BlockchainTestFiller, - pre: Dict[Address, Account], + pre: Alloc, blocks: List[Block], ): """ @@ -705,13 +730,15 @@ def test_deposit( pytest.param( [ DepositTransaction( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=1_000_000_000, - signature=0x03, - index=0x0, - ), + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=0x0, + ) + ], ), ], [], @@ -721,13 +748,15 @@ def test_deposit( pytest.param( [ DepositTransaction( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=1_000_000_000, - signature=0x03, - index=0x0, - ), + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=0x0, + ) + ], ), ], [ @@ -745,13 +774,15 @@ def test_deposit( pytest.param( [ DepositTransaction( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=1_000_000_000, - signature=0x03, - index=0x0, - ), + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=0x0, + ) + ], ), ], [ @@ -769,13 +800,15 @@ def test_deposit( pytest.param( [ DepositTransaction( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=1_000_000_000, - signature=0x03, - index=0x0, - ), + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=0x0, + ) + ], ), ], [ @@ -793,13 +826,15 @@ def test_deposit( pytest.param( [ DepositTransaction( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=1_000_000_000, - signature=0x03, - index=0x0, - ), + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=0x0, + ) + ], ), ], [ @@ -817,13 +852,15 @@ def test_deposit( pytest.param( [ DepositTransaction( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=1_000_000_000, - signature=0x03, - index=0x0, - ), + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=0x0, + ) + ], ), ], [ @@ -841,22 +878,22 @@ def test_deposit( pytest.param( [ DepositTransaction( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=1_000_000_000, - signature=0x03, - index=0x0, - ), - ), - DepositTransaction( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=1_000_000_000, - signature=0x03, - index=0x1, - ), + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=0x0, + ), + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=0x1, + ), + ], ), ], [ @@ -881,13 +918,15 @@ def test_deposit( pytest.param( [ DepositTransaction( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=1_000_000_000, - signature=0x03, - index=0x0, - ), + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=0x0, + ) + ], ), ], [ @@ -913,7 +952,7 @@ def test_deposit( ) def test_deposit_negative( blockchain_test: BlockchainTestFiller, - pre: Dict[Address, Account], + pre: Alloc, blocks: List[Block], ): """ diff --git a/tests/prague/eip7002_el_triggerable_withdrawals/conftest.py b/tests/prague/eip7002_el_triggerable_withdrawals/conftest.py index 39deca64e7..00f8a336c1 100644 --- a/tests/prague/eip7002_el_triggerable_withdrawals/conftest.py +++ b/tests/prague/eip7002_el_triggerable_withdrawals/conftest.py @@ -1,18 +1,33 @@ """ Fixtures for the EIP-7002 deposit tests. """ -from typing import Dict, List +from typing import List import pytest -from ethereum_test_tools import Account, Address, Block, Header +from ethereum_test_tools import Alloc, Block, Header from .helpers import WithdrawalRequest, WithdrawalRequestInteractionBase from .spec import Spec +@pytest.fixture +def update_pre( + pre: Alloc, + blocks_withdrawal_requests: List[List[WithdrawalRequestInteractionBase]], +): + """ + Initial state of the accounts. Every deposit transaction defines their own pre-state + requirements, and this fixture aggregates them all. + """ + for requests in blocks_withdrawal_requests: + for r in requests: + r.update_pre(pre) + + @pytest.fixture def included_requests( + update_pre: None, # Fixture is used for its side effects blocks_withdrawal_requests: List[List[WithdrawalRequestInteractionBase]], ) -> List[List[WithdrawalRequest]]: """ @@ -45,45 +60,19 @@ def included_requests( return per_block_included_requests -@pytest.fixture -def pre( - blocks_withdrawal_requests: List[List[WithdrawalRequestInteractionBase]], -) -> Dict[Address, Account]: - """ - Initial state of the accounts. Every withdrawal transaction defines their own pre-state - requirements, and this fixture aggregates them all. - """ - pre: Dict[Address, Account] = {} - for requests in blocks_withdrawal_requests: - for d in requests: - d.update_pre(pre) - return pre - - @pytest.fixture def blocks( + update_pre: None, # Fixture is used for its side effects blocks_withdrawal_requests: List[List[WithdrawalRequestInteractionBase]], included_requests: List[List[WithdrawalRequest]], ) -> List[Block]: """ Return the list of blocks that should be included in the test. """ - blocks: List[Block] = [] - address_nonce: Dict[Address, int] = {} - for i in range(len(blocks_withdrawal_requests)): - txs = [] - for r in blocks_withdrawal_requests[i]: - nonce = 0 - if r.sender_account.address in address_nonce: - nonce = address_nonce[r.sender_account.address] - txs.append(r.transaction(nonce)) - address_nonce[r.sender_account.address] = nonce + 1 - blocks.append( - Block( - txs=txs, - header_verify=Header( - requests_root=included_requests[i], - ), - ) + return [ + Block( + txs=sum((r.transactions() for r in block_requests), []), + header_verify=Header(requests_root=included_requests[i]), ) - return blocks + for i, block_requests in enumerate(blocks_withdrawal_requests) + ] diff --git a/tests/prague/eip7002_el_triggerable_withdrawals/helpers.py b/tests/prague/eip7002_el_triggerable_withdrawals/helpers.py index 483e3f9de8..0b0c97b687 100644 --- a/tests/prague/eip7002_el_triggerable_withdrawals/helpers.py +++ b/tests/prague/eip7002_el_triggerable_withdrawals/helpers.py @@ -4,34 +4,16 @@ from dataclasses import dataclass, field from functools import cached_property from itertools import count -from typing import Callable, ClassVar, Dict, List +from typing import Callable, ClassVar, List -from ethereum_test_tools import Account, Address +from ethereum_test_tools import EOA, Address, Alloc from ethereum_test_tools import Opcodes as Op -from ethereum_test_tools import ( - TestAddress, - TestAddress2, - TestPrivateKey, - TestPrivateKey2, - Transaction, -) +from ethereum_test_tools import Transaction from ethereum_test_tools import WithdrawalRequest as WithdrawalRequestBase from .spec import Spec -@dataclass -class SenderAccount: - """Test sender account descriptor.""" - - address: Address - key: str - - -TestAccount1 = SenderAccount(TestAddress, TestPrivateKey) -TestAccount2 = SenderAccount(TestAddress2, TestPrivateKey2) - - class WithdrawalRequest(WithdrawalRequestBase): """ Class used to describe a withdrawal request in a test. @@ -92,18 +74,20 @@ class WithdrawalRequestInteractionBase: """ Balance of the account that sends the transaction. """ - sender_account: SenderAccount = field( - default_factory=lambda: SenderAccount(TestAddress, TestPrivateKey) - ) + sender_account: EOA | None = None """ Account that will send the transaction. """ + requests: List[WithdrawalRequest] + """ + Withdrawal request to be included in the block. + """ - def transaction(self, nonce: int) -> Transaction: + def transactions(self) -> List[Transaction]: """Return a transaction for the withdrawal request.""" raise NotImplementedError - def update_pre(self, base_pre: Dict[Address, Account]): + def update_pre(self, pre: Alloc): """Return the pre-state of the account.""" raise NotImplementedError @@ -116,47 +100,39 @@ def valid_requests(self, current_minimum_fee: int) -> List[WithdrawalRequest]: class WithdrawalRequestTransaction(WithdrawalRequestInteractionBase): """Class used to describe a withdrawal request originated from an externally owned account.""" - request: WithdrawalRequest - """ - Withdrawal request to be requested by the transaction. - """ - - def transaction(self, nonce: int) -> Transaction: + def transactions(self) -> List[Transaction]: """Return a transaction for the withdrawal request.""" - return Transaction( - nonce=nonce, - gas_limit=self.request.gas_limit, - gas_price=0x07, - to=self.request.interaction_contract_address, - value=self.request.value, - data=self.request.calldata, - secret_key=self.sender_account.key, - ) + assert self.sender_account is not None, "Sender account not initialized" + return [ + Transaction( + gas_limit=request.gas_limit, + gas_price=0x07, + to=request.interaction_contract_address, + value=request.value, + data=request.calldata, + sender=self.sender_account, + ) + for request in self.requests + ] - def update_pre(self, base_pre: Dict[Address, Account]): + def update_pre(self, pre: Alloc): """Return the pre-state of the account.""" - base_pre.update( - { - self.sender_account.address: Account(balance=self.sender_balance), - } - ) + self.sender_account = pre.fund_eoa(self.sender_balance) def valid_requests(self, current_minimum_fee: int) -> List[WithdrawalRequest]: """Return the list of withdrawal requests that are valid.""" - if self.request.valid and self.request.fee >= current_minimum_fee: - return [self.request.with_source_address(self.sender_account.address)] - return [] + assert self.sender_account is not None, "Sender account not initialized" + return [ + request.with_source_address(self.sender_account) + for request in self.requests + if request.valid and request.fee >= current_minimum_fee + ] @dataclass(kw_only=True) class WithdrawalRequestContract(WithdrawalRequestInteractionBase): """Class used to describe a deposit originated from a contract.""" - request: List[WithdrawalRequest] | WithdrawalRequest - """ - Withdrawal request or list of withdrawal requests to be requested by the contract. - """ - tx_gas_limit: int = 1_000_000 """ Gas limit for the transaction. @@ -166,10 +142,14 @@ class WithdrawalRequestContract(WithdrawalRequestInteractionBase): """ Balance of the contract that will make the call to the pre-deploy contract. """ - contract_address: int = 0x200 + contract_address: Address | None = None """ Address of the contract that will make the call to the pre-deploy contract. """ + entry_address: Address | None = None + """ + Address to send the transaction to. + """ call_type: Op = field(default_factory=lambda: Op.CALL) """ @@ -184,13 +164,6 @@ class WithdrawalRequestContract(WithdrawalRequestInteractionBase): Extra code to be added to the contract code. """ - @property - def requests(self) -> List[WithdrawalRequest]: - """Return the list of withdrawal requests.""" - if not isinstance(self.request, List): - return [self.request] - return self.request - @property def contract_code(self) -> bytes: """Contract code used by the relay contract.""" @@ -212,71 +185,52 @@ def contract_code(self) -> bytes: current_offset += len(r.calldata) return code + self.extra_code - def transaction(self, nonce: int) -> Transaction: + def transactions(self) -> List[Transaction]: """Return a transaction for the deposit request.""" - return Transaction( - nonce=nonce, - gas_limit=self.tx_gas_limit, - gas_price=0x07, - to=self.entry_address(), - value=0, - data=b"".join(r.calldata for r in self.requests), - secret_key=self.sender_account.key, - ) - - def entry_address(self) -> Address: - """Return the address of the contract entry point.""" - if self.call_depth == 2: - return Address(self.contract_address) - elif self.call_depth > 2: - return Address(self.contract_address + self.call_depth - 2) - raise ValueError("Invalid call depth") - - def extra_contracts(self) -> Dict[Address, Account]: - """Extra contracts used to simulate call depth.""" - if self.call_depth <= 2: - return {} - return { - Address(self.contract_address + i): Account( - balance=self.contract_balance, - code=Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) - + Op.POP( - Op.CALL( - Op.GAS, - self.contract_address + i - 1, - 0, - 0, - Op.CALLDATASIZE, - 0, - 0, - ) - ), - nonce=1, + assert self.entry_address is not None, "Entry address not initialized" + return [ + Transaction( + gas_limit=self.tx_gas_limit, + gas_price=0x07, + to=self.entry_address, + value=0, + data=b"".join(r.calldata for r in self.requests), + sender=self.sender_account, ) - for i in range(1, self.call_depth - 1) - } + ] - def update_pre(self, base_pre: Dict[Address, Account]): + def update_pre(self, pre: Alloc): """Return the pre-state of the account.""" - while Address(self.contract_address) in base_pre: - self.contract_address += 0x100 - base_pre.update( - { - self.sender_account.address: Account(balance=self.sender_balance), - Address(self.contract_address): Account( - balance=self.contract_balance, code=self.contract_code, nonce=1 - ), - } + self.sender_account = pre.fund_eoa(self.sender_balance) + self.contract_address = pre.deploy_contract( + code=self.contract_code, balance=self.contract_balance ) - base_pre.update(self.extra_contracts()) + self.entry_address = self.contract_address + if self.call_depth > 2: + for _ in range(1, self.call_depth - 1): + self.entry_address = pre.deploy_contract( + code=Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + + Op.POP( + Op.CALL( + Op.GAS, + self.entry_address, + 0, + 0, + Op.CALLDATASIZE, + 0, + 0, + ) + ) + ) def valid_requests(self, current_minimum_fee: int) -> List[WithdrawalRequest]: """Return the list of withdrawal requests that are valid.""" - valid_requests: List[WithdrawalRequest] = [] - for r in self.requests: - if r.valid and r.value >= current_minimum_fee: - valid_requests.append(r.with_source_address(Address(self.contract_address))) - return valid_requests + assert self.contract_address is not None, "Contract address not initialized" + return [ + r.with_source_address(self.contract_address) + for r in self.requests + if r.valid and r.value >= current_minimum_fee + ] def get_n_fee_increments(n: int) -> List[int]: @@ -305,7 +259,6 @@ def get_n_fee_increment_blocks(n: int) -> List[List[WithdrawalRequestContract]]: """ blocks = [] previous_excess = 0 - nonce = count(0) withdrawal_index = 0 previous_fee = 0 for required_excess_withdrawals in get_n_fee_increments(n): @@ -314,13 +267,12 @@ def get_n_fee_increment_blocks(n: int) -> List[List[WithdrawalRequestContract]]: + Spec.TARGET_WITHDRAWAL_REQUESTS_PER_BLOCK - previous_excess ) - contract_address = next(nonce) fee = Spec.get_fee(previous_excess) assert fee > previous_fee blocks.append( [ WithdrawalRequestContract( - request=[ + requests=[ WithdrawalRequest( validator_public_key=i, amount=0, @@ -328,8 +280,6 @@ def get_n_fee_increment_blocks(n: int) -> List[List[WithdrawalRequestContract]]: ) for i in range(withdrawal_index, withdrawal_index + withdrawals_required) ], - # Increment the contract address to avoid overwriting the previous one - contract_address=0x200 + (contract_address * 0x100), ) ], ) diff --git a/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests.py b/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests.py index bd9bbd3bf2..7f7687f442 100644 --- a/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests.py +++ b/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests.py @@ -4,13 +4,13 @@ """ # noqa: E501 -from typing import Dict, List +from typing import List import pytest from ethereum_test_tools import ( - Account, Address, + Alloc, Block, BlockchainTestFiller, BlockException, @@ -22,7 +22,6 @@ from ethereum_test_tools import TestAddress, TestAddress2 from .helpers import ( - TestAccount2, WithdrawalRequest, WithdrawalRequestContract, WithdrawalRequestInteractionBase, @@ -44,11 +43,13 @@ [ [ WithdrawalRequestTransaction( - request=WithdrawalRequest( - validator_public_key=0x01, - amount=0, - fee=Spec.get_fee(0), - ), + requests=[ + WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=Spec.get_fee(0), + ) + ], ), ], ], @@ -58,11 +59,13 @@ [ [ WithdrawalRequestTransaction( - request=WithdrawalRequest( - validator_public_key=0x01, - amount=0, - fee=0, - ), + requests=[ + WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=0, + ) + ], ), ], ], @@ -72,13 +75,15 @@ [ [ WithdrawalRequestTransaction( - request=WithdrawalRequest( - validator_public_key=0x01, - amount=0, - fee=Spec.get_fee(0), - calldata_modifier=lambda x: x[:-1], - valid=False, - ), + requests=[ + WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=Spec.get_fee(0), + calldata_modifier=lambda x: x[:-1], + valid=False, + ) + ], ), ], ], @@ -88,13 +93,15 @@ [ [ WithdrawalRequestTransaction( - request=WithdrawalRequest( - validator_public_key=0x01, - amount=0, - fee=Spec.get_fee(0), - calldata_modifier=lambda x: x + b"\x00", - valid=False, - ), + requests=[ + WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=Spec.get_fee(0), + calldata_modifier=lambda x: x + b"\x00", + valid=False, + ) + ], ), ], ], @@ -104,18 +111,18 @@ [ [ WithdrawalRequestTransaction( - request=WithdrawalRequest( - validator_public_key=0x01, - amount=0, - fee=Spec.get_fee(0), - ), - ), - WithdrawalRequestTransaction( - request=WithdrawalRequest( - validator_public_key=0x02, - amount=Spec.MAX_AMOUNT - 1, - fee=Spec.get_fee(0), - ), + requests=[ + WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=Spec.get_fee(0), + ), + WithdrawalRequest( + validator_public_key=0x02, + amount=Spec.MAX_AMOUNT - 1, + fee=Spec.get_fee(0), + ), + ], ), ], ], @@ -125,19 +132,22 @@ [ [ WithdrawalRequestTransaction( - request=WithdrawalRequest( - validator_public_key=0x01, - amount=0, - fee=Spec.get_fee(0), - ), + requests=[ + WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=Spec.get_fee(0), + ) + ], ), WithdrawalRequestTransaction( - request=WithdrawalRequest( - validator_public_key=0x02, - amount=Spec.MAX_AMOUNT - 1, - fee=Spec.get_fee(0), - ), - sender_account=TestAccount2, + requests=[ + WithdrawalRequest( + validator_public_key=0x02, + amount=Spec.MAX_AMOUNT - 1, + fee=Spec.get_fee(0), + ) + ], ), ], ], @@ -147,13 +157,15 @@ [ [ WithdrawalRequestTransaction( - request=WithdrawalRequest( - validator_public_key=i + 1, - amount=0 if i % 2 == 0 else Spec.MAX_AMOUNT, - fee=Spec.get_fee(0), - ), + requests=[ + WithdrawalRequest( + validator_public_key=i + 1, + amount=0 if i % 2 == 0 else Spec.MAX_AMOUNT, + fee=Spec.get_fee(0), + ) + for i in range(Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK) + ], ) - for i in range(Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK) ], ], id="single_block_max_withdrawal_requests_from_eoa", @@ -162,18 +174,18 @@ [ [ WithdrawalRequestTransaction( - request=WithdrawalRequest( - validator_public_key=0x01, - amount=0, - fee=0, - ), - ), - WithdrawalRequestTransaction( - request=WithdrawalRequest( - validator_public_key=0x02, - amount=Spec.MAX_AMOUNT - 1, - fee=Spec.get_fee(0), - ), + requests=[ + WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=0, + ), + WithdrawalRequest( + validator_public_key=0x02, + amount=Spec.MAX_AMOUNT - 1, + fee=Spec.get_fee(0), + ), + ] ), ], ], @@ -183,18 +195,18 @@ [ [ WithdrawalRequestTransaction( - request=WithdrawalRequest( - validator_public_key=0x01, - amount=0, - fee=Spec.get_fee(0), - ), - ), - WithdrawalRequestTransaction( - request=WithdrawalRequest( - validator_public_key=0x02, - amount=Spec.MAX_AMOUNT - 1, - fee=0, - ), + requests=[ + WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=Spec.get_fee(0), + ), + WithdrawalRequest( + validator_public_key=0x02, + amount=Spec.MAX_AMOUNT - 1, + fee=0, + ), + ] ), ], ], @@ -204,21 +216,21 @@ [ [ WithdrawalRequestTransaction( - request=WithdrawalRequest( - validator_public_key=0x01, - amount=0, - fee=Spec.get_fee(0), - # Value obtained from trace minus one - gas_limit=114_247 - 1, - valid=False, - ), - ), - WithdrawalRequestTransaction( - request=WithdrawalRequest( - validator_public_key=0x02, - amount=0, - fee=Spec.get_fee(0), - ), + requests=[ + WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=Spec.get_fee(0), + # Value obtained from trace minus one + gas_limit=114_247 - 1, + valid=False, + ), + WithdrawalRequest( + validator_public_key=0x02, + amount=0, + fee=Spec.get_fee(0), + ), + ] ), ], ], @@ -228,21 +240,21 @@ [ [ WithdrawalRequestTransaction( - request=WithdrawalRequest( - validator_public_key=0x01, - amount=0, - fee=Spec.get_fee(0), - ), - ), - WithdrawalRequestTransaction( - request=WithdrawalRequest( - validator_public_key=0x02, - amount=0, - fee=Spec.get_fee(0), - # Value obtained from trace minus one - gas_limit=80_047 - 1, - valid=False, - ), + requests=[ + WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=Spec.get_fee(0), + ), + WithdrawalRequest( + validator_public_key=0x02, + amount=0, + fee=Spec.get_fee(0), + # Value obtained from trace minus one + gas_limit=80_047 - 1, + valid=False, + ), + ] ), ], ], @@ -253,13 +265,15 @@ # Block 1 [ WithdrawalRequestTransaction( - request=WithdrawalRequest( - validator_public_key=i + 1, - amount=0 if i % 2 == 0 else Spec.MAX_AMOUNT, - fee=Spec.get_fee(0), - ), + requests=[ + WithdrawalRequest( + validator_public_key=i + 1, + amount=0 if i % 2 == 0 else Spec.MAX_AMOUNT, + fee=Spec.get_fee(0), + ) + for i in range(Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK * 2) + ] ) - for i in range(Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK * 2) ], # Block 2, no new withdrawal requests, but queued requests from previous block [], @@ -272,11 +286,13 @@ [ [ WithdrawalRequestContract( - request=WithdrawalRequest( - validator_public_key=0x01, - amount=0, - fee=Spec.get_fee(0), - ), + requests=[ + WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=Spec.get_fee(0), + ), + ] ), ], ], @@ -286,7 +302,7 @@ [ [ WithdrawalRequestContract( - request=[ + requests=[ WithdrawalRequest( validator_public_key=i + 1, amount=Spec.MAX_AMOUNT - 1 if i % 2 == 0 else 0, @@ -303,7 +319,7 @@ [ [ WithdrawalRequestContract( - request=[ + requests=[ WithdrawalRequest( validator_public_key=1, amount=Spec.MAX_AMOUNT, @@ -327,7 +343,7 @@ [ [ WithdrawalRequestContract( - request=[ + requests=[ WithdrawalRequest( validator_public_key=i + 1, amount=Spec.MAX_AMOUNT - 1 if i % 2 == 0 else 0, @@ -353,7 +369,7 @@ [ [ WithdrawalRequestContract( - request=[ + requests=[ WithdrawalRequest( validator_public_key=1, amount=Spec.MAX_AMOUNT - 1, @@ -381,7 +397,7 @@ [ [ WithdrawalRequestContract( - request=[ + requests=[ WithdrawalRequest( validator_public_key=i + 1, amount=Spec.MAX_AMOUNT - 1 if i % 2 == 0 else 0, @@ -409,7 +425,7 @@ [ [ WithdrawalRequestContract( - request=[ + requests=[ WithdrawalRequest( validator_public_key=i + 1, amount=Spec.MAX_AMOUNT - 1 if i % 2 == 0 else 0, @@ -428,7 +444,7 @@ [ [ WithdrawalRequestContract( - request=[ + requests=[ WithdrawalRequest( validator_public_key=i + 1, amount=Spec.MAX_AMOUNT - 1 if i % 2 == 0 else 0, @@ -452,30 +468,36 @@ [ [ WithdrawalRequestContract( - request=WithdrawalRequest( - validator_public_key=0x01, - amount=0, - fee=Spec.get_fee(0), - valid=False, - ), + requests=[ + WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=Spec.get_fee(0), + valid=False, + ) + ], call_type=Op.DELEGATECALL, ), WithdrawalRequestContract( - request=WithdrawalRequest( - validator_public_key=0x01, - amount=0, - fee=Spec.get_fee(0), - valid=False, - ), + requests=[ + WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=Spec.get_fee(0), + valid=False, + ) + ], call_type=Op.STATICCALL, ), WithdrawalRequestContract( - request=WithdrawalRequest( - validator_public_key=0x01, - amount=0, - fee=Spec.get_fee(0), - valid=False, - ), + requests=[ + WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=Spec.get_fee(0), + valid=False, + ) + ], call_type=Op.CALLCODE, ), ], @@ -487,7 +509,7 @@ def test_withdrawal_requests( blockchain_test: BlockchainTestFiller, blocks: List[Block], - pre: Dict[Address, Account], + pre: Alloc, ): """ Test making a withdrawal request to the beacon chain. @@ -518,11 +540,13 @@ def test_withdrawal_requests( pytest.param( [ WithdrawalRequestTransaction( - request=WithdrawalRequest( - validator_public_key=0x01, - amount=0, - fee=Spec.get_fee(0), - ), + requests=[ + WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=Spec.get_fee(0), + ), + ] ), ], [], @@ -532,11 +556,13 @@ def test_withdrawal_requests( pytest.param( [ WithdrawalRequestTransaction( - request=WithdrawalRequest( - validator_public_key=0x01, - amount=0, - fee=Spec.get_fee(0), - ), + requests=[ + WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=Spec.get_fee(0), + ), + ] ), ], [ @@ -552,11 +578,13 @@ def test_withdrawal_requests( pytest.param( [ WithdrawalRequestTransaction( - request=WithdrawalRequest( - validator_public_key=0x01, - amount=0, - fee=Spec.get_fee(0), - ), + requests=[ + WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=Spec.get_fee(0), + ) + ], ), ], [ @@ -572,11 +600,13 @@ def test_withdrawal_requests( pytest.param( [ WithdrawalRequestTransaction( - request=WithdrawalRequest( - validator_public_key=0x01, - amount=0, - fee=Spec.get_fee(0), - ), + requests=[ + WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=Spec.get_fee(0), + ) + ], ), ], [ @@ -592,18 +622,18 @@ def test_withdrawal_requests( pytest.param( [ WithdrawalRequestTransaction( - request=WithdrawalRequest( - validator_public_key=0x01, - amount=0, - fee=Spec.get_fee(0), - ), - ), - WithdrawalRequestTransaction( - request=WithdrawalRequest( - validator_public_key=0x02, - amount=0, - fee=Spec.get_fee(0), - ), + requests=[ + WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=Spec.get_fee(0), + ), + WithdrawalRequest( + validator_public_key=0x02, + amount=0, + fee=Spec.get_fee(0), + ), + ], ), ], [ @@ -624,11 +654,13 @@ def test_withdrawal_requests( pytest.param( [ WithdrawalRequestTransaction( - request=WithdrawalRequest( - validator_public_key=0x01, - amount=0, - fee=Spec.get_fee(0), - ), + requests=[ + WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=Spec.get_fee(0), + ) + ], ), ], [ @@ -649,6 +681,7 @@ def test_withdrawal_requests( ], ) def test_withdrawal_requests_negative( + pre: Alloc, blockchain_test: BlockchainTestFiller, requests: List[WithdrawalRequestInteractionBase], block_body_override_requests: List[WithdrawalRequest], @@ -658,6 +691,9 @@ def test_withdrawal_requests_negative( Test blocks where the requests list and the actual withdrawal requests that happened in the block's transactions do not match. """ + for d in requests: + d.update_pre(pre) + # No previous block so fee is the base fee = 1 current_block_requests = [] @@ -665,25 +701,13 @@ def test_withdrawal_requests_negative( current_block_requests += w.valid_requests(fee) included_requests = current_block_requests[: Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK] - pre: Dict[Address, Account] = {} - for d in requests: - d.update_pre(pre) - - address_nonce: Dict[Address, int] = {} - txs = [] - for r in requests: - nonce = 0 - if r.sender_account.address in address_nonce: - nonce = address_nonce[r.sender_account.address] - txs.append(r.transaction(nonce)) - address_nonce[r.sender_account.address] = nonce + 1 blockchain_test( genesis_environment=Environment(), pre=pre, post={}, blocks=[ Block( - txs=txs, + txs=sum((r.transactions() for r in requests), []), header_verify=Header( requests_root=included_requests, ), diff --git a/tests/prague/eip7685_general_purpose_el_requests/conftest.py b/tests/prague/eip7685_general_purpose_el_requests/conftest.py index 16cb104a7e..fe06e89b2e 100644 --- a/tests/prague/eip7685_general_purpose_el_requests/conftest.py +++ b/tests/prague/eip7685_general_purpose_el_requests/conftest.py @@ -2,11 +2,11 @@ Fixtures for the EIP-7685 deposit tests. """ -from typing import Dict, List +from typing import List import pytest -from ethereum_test_tools import Account, Address, Block, BlockException, Header, Transaction +from ethereum_test_tools import Alloc, Block, BlockException, Header from ..eip6110_deposits.helpers import DepositInteractionBase, DepositRequest from ..eip7002_el_triggerable_withdrawals.helpers import ( @@ -15,36 +15,6 @@ ) -@pytest.fixture -def pre( - requests: List[DepositInteractionBase | WithdrawalRequestInteractionBase], -) -> Dict[Address, Account]: - """ - Initial state of the accounts. Every deposit transaction defines their own pre-state - requirements, and this fixture aggregates them all. - """ - pre: Dict[Address, Account] = {} - for d in requests: - d.update_pre(pre) - return pre - - -@pytest.fixture -def txs( - requests: List[DepositInteractionBase | WithdrawalRequestInteractionBase], -) -> List[Transaction]: - """List of transactions to include in the block.""" - address_nonce: Dict[Address, int] = {} - txs = [] - for r in requests: - nonce = 0 - if r.sender_account.address in address_nonce: - nonce = address_nonce[r.sender_account.address] - txs.append(r.transaction(nonce)) - address_nonce[r.sender_account.address] = nonce + 1 - return txs - - @pytest.fixture def block_body_override_requests() -> List[DepositRequest] | None: """List of requests that overwrite the requests in the header. None by default.""" @@ -59,9 +29,9 @@ def exception() -> BlockException | None: @pytest.fixture def blocks( + pre: Alloc, requests: List[DepositInteractionBase | WithdrawalRequestInteractionBase], block_body_override_requests: List[DepositRequest | WithdrawalRequest] | None, - txs: List[Transaction], exception: BlockException | None, ) -> List[Block]: """List of blocks that comprise the test.""" @@ -70,6 +40,7 @@ def blocks( # Single block therefore base fee withdrawal_request_fee = 1 for r in requests: + r.update_pre(pre) if isinstance(r, DepositInteractionBase): included_deposit_requests += r.valid_requests(10**18) elif isinstance(r, WithdrawalRequestInteractionBase): @@ -77,7 +48,7 @@ def blocks( return [ Block( - txs=txs, + txs=sum((r.transactions() for r in requests), []), header_verify=Header( requests_root=included_deposit_requests + included_withdrawal_requests, ), diff --git a/tests/prague/eip7685_general_purpose_el_requests/test_deposits_withdrawals.py b/tests/prague/eip7685_general_purpose_el_requests/test_deposits_withdrawals.py index b7356fdce8..1f011c6192 100644 --- a/tests/prague/eip7685_general_purpose_el_requests/test_deposits_withdrawals.py +++ b/tests/prague/eip7685_general_purpose_el_requests/test_deposits_withdrawals.py @@ -4,13 +4,12 @@ """ # noqa: E501 -from typing import Dict, List +from typing import List import pytest from ethereum_test_tools import ( - Account, - Address, + Alloc, Block, BlockchainTestFiller, BlockException, @@ -42,20 +41,24 @@ pytest.param( [ DepositTransaction( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x0, - ), + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ) + ], ), WithdrawalRequestTransaction( - request=WithdrawalRequest( - validator_public_key=0x01, - amount=0, - fee=1, - ), + requests=[ + WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=1, + ) + ], ), ], id="single_deposit_from_eoa_single_withdrawal_from_eoa", @@ -63,20 +66,24 @@ pytest.param( [ WithdrawalRequestTransaction( - request=WithdrawalRequest( - validator_public_key=0x01, - amount=0, - fee=1, - ), + requests=[ + WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=1, + ) + ], ), DepositTransaction( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x0, - ), + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ) + ], ), ], id="single_withdrawal_from_eoa_single_deposit_from_eoa", @@ -84,29 +91,35 @@ pytest.param( [ DepositTransaction( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x0, - ), + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ) + ], ), WithdrawalRequestTransaction( - request=WithdrawalRequest( - validator_public_key=0x01, - amount=0, - fee=1, - ), + requests=[ + WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=1, + ) + ], ), DepositTransaction( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x1, - ), + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x1, + ) + ], ), ], id="two_deposits_from_eoa_single_withdrawal_from_eoa", @@ -114,27 +127,33 @@ pytest.param( [ WithdrawalRequestTransaction( - request=WithdrawalRequest( - validator_public_key=0x01, - amount=0, - fee=1, - ), + requests=[ + WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=1, + ) + ], ), DepositTransaction( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x0, - ), + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ) + ], ), WithdrawalRequestTransaction( - request=WithdrawalRequest( - validator_public_key=0x01, - amount=1, - fee=1, - ), + requests=[ + WithdrawalRequest( + validator_public_key=0x01, + amount=1, + fee=1, + ) + ], ), ], id="two_withdrawals_from_eoa_single_deposit_from_eoa", @@ -142,20 +161,24 @@ pytest.param( [ DepositContract( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x0, - ), + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ) + ], ), WithdrawalRequestContract( - request=WithdrawalRequest( - validator_public_key=0x01, - amount=0, - fee=1, - ), + requests=[ + WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=1, + ) + ], ), ], id="single_deposit_from_contract_single_withdrawal_from_contract", @@ -163,20 +186,24 @@ pytest.param( [ WithdrawalRequestContract( - request=WithdrawalRequest( - validator_public_key=0x01, - amount=0, - fee=1, - ), + requests=[ + WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=1, + ) + ], ), DepositContract( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x0, - ), + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ) + ], ), ], id="single_withdrawal_from_contract_single_deposit_from_contract", @@ -186,7 +213,7 @@ ) def test_valid_deposit_withdrawal_requests( blockchain_test: BlockchainTestFiller, - pre: Dict[Address, Account], + pre: Alloc, blocks: List[Block], ): """ @@ -209,12 +236,12 @@ def test_valid_deposit_withdrawal_requests( ) def test_valid_deposit_withdrawal_request_from_same_tx( blockchain_test: BlockchainTestFiller, + pre: Alloc, deposit_first: bool, ): """ Test making a deposit to the beacon chain deposit contract and a withdrawal in the same tx. """ - contract_address = 0x200 withdrawal_request_fee = 1 deposit_request = DepositRequest( pubkey=0x01, @@ -226,7 +253,6 @@ def test_valid_deposit_withdrawal_request_from_same_tx( withdrawal_request = WithdrawalRequest( validator_public_key=0x01, amount=0, - source_address=contract_address, ) if deposit_first: calldata = deposit_request.calldata + withdrawal_request.calldata @@ -283,37 +309,34 @@ def test_valid_deposit_withdrawal_request_from_same_tx( ) ) - pre = { - TestAddress: Account( - balance=10**18, - ), - contract_address: Account( - code=contract_code, - balance=deposit_request.value + withdrawal_request_fee, - ), - } + sender = pre.fund_eoa(10**18) + contract_address = pre.deploy_contract( + code=contract_code, + balance=deposit_request.value + withdrawal_request_fee, + ) + withdrawal_request = withdrawal_request.with_source_address(contract_address) tx = Transaction( - nonce=0, gas_limit=1_000_000, gas_price=0x07, to=contract_address, value=0, data=calldata, - ) - - block = Block( - txs=[tx], - header_verify=Header( - requests_root=[deposit_request, withdrawal_request], - ), + sender=sender, ) blockchain_test( genesis_environment=Environment(), pre=pre, post={}, - blocks=[block], + blocks=[ + Block( + txs=[tx], + header_verify=Header( + requests_root=[deposit_request, withdrawal_request], + ), + ) + ], ) @@ -323,20 +346,24 @@ def test_valid_deposit_withdrawal_request_from_same_tx( pytest.param( [ WithdrawalRequestTransaction( - request=WithdrawalRequest( - validator_public_key=0x01, - amount=0, - fee=1, - ), + requests=[ + WithdrawalRequest( + validator_public_key=0x01, + amount=0, + fee=1, + ) + ], ), DepositTransaction( - request=DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x0, - ), + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ) + ], ), ], [ @@ -361,7 +388,7 @@ def test_valid_deposit_withdrawal_request_from_same_tx( ) def test_invalid_deposit_withdrawal_requests( blockchain_test: BlockchainTestFiller, - pre: Dict[Address, Account], + pre: Alloc, blocks: List[Block], ): """ diff --git a/tests/prague/eip7692_eof_v1/eip3540_eof_v1/test_calls.py b/tests/prague/eip7692_eof_v1/eip3540_eof_v1/test_calls.py index c2408e026d..6ac51fed4a 100644 --- a/tests/prague/eip7692_eof_v1/eip3540_eof_v1/test_calls.py +++ b/tests/prague/eip7692_eof_v1/eip3540_eof_v1/test_calls.py @@ -6,16 +6,16 @@ import pytest from ethereum_test_tools import ( + EOA, Account, Address, + Alloc, Environment, StateTestFiller, Storage, - TestAddress, Transaction, ) from ethereum_test_tools.eof.v1 import Container, Section -from ethereum_test_tools.eof.v1.constants import NON_RETURNING_SECTION from ethereum_test_tools.vm.opcode import Opcodes as Op from .. import EOF_FORK_NAME @@ -47,13 +47,20 @@ sections=[ Section.Code( code=Op.SSTORE(slot_caller, Op.CALLER()) + Op.STOP, - code_outputs=NON_RETURNING_SECTION, max_stack_height=2, ) ] ) +@pytest.fixture +def sender(pre: Alloc) -> EOA: + """ + The sender of the transaction + """ + return pre.fund_eoa() + + @pytest.mark.parametrize( ["opcode", "suffix"], [ @@ -66,35 +73,23 @@ ) def test_legacy_calls_eof_sstore( state_test: StateTestFiller, + pre: Alloc, + sender: EOA, opcode: Op, suffix: list[int], ): """Test legacy contracts calling EOF contracts that use SSTORE""" env = Environment() - calling_contract_address = Address(0x1000000) - destination_contract_address = Address(0x1000001) + destination_contract_address = pre.deploy_contract(contract_eof_sstore) caller_contract = Op.SSTORE( slot_call_result, opcode(Op.GAS, destination_contract_address, *suffix) ) + Op.SSTORE(slot_code_worked, value_code_worked) - pre = { - TestAddress: Account( - balance=1000000000000000000000, - nonce=1, - ), - calling_contract_address: Account( - code=caller_contract, - nonce=1, - ), - destination_contract_address: Account( - code=contract_eof_sstore, - nonce=1, - ), - } + calling_contract_address = pre.deploy_contract(caller_contract) tx = Transaction( - nonce=1, + sender=sender, to=Address(calling_contract_address), gas_limit=50000000, gas_price=10, @@ -113,7 +108,7 @@ def test_legacy_calls_eof_sstore( if opcode == Op.CALL: destination_storage[slot_caller] = calling_contract_address elif opcode == Op.DELEGATECALL: - calling_storage[slot_caller] = TestAddress + calling_storage[slot_caller] = sender elif opcode == Op.CALLCODE: calling_storage[slot_caller] = calling_contract_address elif opcode == Op.STATICCALL: @@ -144,13 +139,23 @@ def test_legacy_calls_eof_sstore( ) def test_legacy_calls_eof_mstore( state_test: StateTestFiller, + pre: Alloc, + sender: EOA, opcode: Op, suffix: list[int], ): """Test legacy contracts calling EOF contracts that only return data""" env = Environment() - calling_contract_address = Address(0x1000000) - destination_contract_address = Address(0x1000001) + destination_contract_code = Container( + sections=[ + Section.Code( + code=Op.MSTORE8(0, int.from_bytes(value_returndata_magic, "big")) + + Op.RETURN(0, len(value_returndata_magic)), + max_stack_height=2, + ) + ] + ) + destination_contract_address = pre.deploy_contract(destination_contract_code) caller_contract = ( Op.SSTORE(slot_call_result, opcode(Op.GAS, destination_contract_address, *suffix)) @@ -159,33 +164,10 @@ def test_legacy_calls_eof_mstore( + Op.SSTORE(slot_returndata, Op.MLOAD(0)) + Op.SSTORE(slot_code_worked, value_code_worked) ) - - pre = { - TestAddress: Account( - balance=1000000000000000000000, - nonce=1, - ), - calling_contract_address: Account( - code=caller_contract, - nonce=1, - ), - destination_contract_address: Account( - code=Container( - sections=[ - Section.Code( - code=Op.MSTORE8(0, int.from_bytes(value_returndata_magic, "big")) - + Op.RETURN(0, len(value_returndata_magic)), - code_outputs=NON_RETURNING_SECTION, - max_stack_height=2, - ) - ] - ), - nonce=1, - ), - } + calling_contract_address = pre.deploy_contract(caller_contract) tx = Transaction( - nonce=1, + sender=sender, to=Address(calling_contract_address), gas_limit=50000000, gas_price=10, @@ -224,13 +206,14 @@ def test_legacy_calls_eof_mstore( ) def test_eof_calls_eof_sstore( state_test: StateTestFiller, + pre: Alloc, + sender: EOA, opcode: Op, suffix: list[int], ): """Test EOF contracts calling EOF contracts that use SSTORE""" env = Environment() - calling_contract_address = Address(0x1000000) - destination_contract_address = Address(0x1000001) + destination_contract_address = pre.deploy_contract(contract_eof_sstore) caller_contract = Container( sections=[ @@ -238,29 +221,14 @@ def test_eof_calls_eof_sstore( code=Op.SSTORE(slot_call_result, opcode(destination_contract_address, *suffix)) + Op.SSTORE(slot_code_worked, value_code_worked) + Op.STOP, - code_outputs=NON_RETURNING_SECTION, max_stack_height=1 + len(suffix), ) ] ) - - pre = { - TestAddress: Account( - balance=1000000000000000000000, - nonce=1, - ), - calling_contract_address: Account( - code=caller_contract, - nonce=1, - ), - destination_contract_address: Account( - code=contract_eof_sstore, - nonce=1, - ), - } + calling_contract_address = pre.deploy_contract(caller_contract) tx = Transaction( - nonce=1, + sender=sender, to=Address(calling_contract_address), gas_limit=50000000, gas_price=10, @@ -279,7 +247,7 @@ def test_eof_calls_eof_sstore( if opcode == Op.EXTCALL: destination_storage[slot_caller] = calling_contract_address elif opcode == Op.EXTDELEGATECALL: - calling_storage[slot_caller] = TestAddress + calling_storage[slot_caller] = sender elif opcode == Op.EXTSTATICCALL: calling_storage[slot_call_result] = value_eof_call_reverted @@ -307,13 +275,23 @@ def test_eof_calls_eof_sstore( ) def test_eof_calls_eof_mstore( state_test: StateTestFiller, + pre: Alloc, + sender: EOA, opcode: Op, suffix: list[int], ): """Test EOF contracts calling EOF contracts that return data""" env = Environment() - calling_contract_address = Address(0x1000000) - destination_contract_address = Address(0x1000001) + destination_contract_code = Container( + sections=[ + Section.Code( + code=Op.MSTORE8(0, int.from_bytes(value_returndata_magic, "big")) + + Op.RETURN(0, 32), + max_stack_height=2, + ) + ] + ) + destination_contract_address = pre.deploy_contract(destination_contract_code) caller_contract = Container( sections=[ @@ -323,38 +301,14 @@ def test_eof_calls_eof_mstore( + Op.SSTORE(slot_returndata, Op.RETURNDATALOAD(0)) + Op.SSTORE(slot_code_worked, value_code_worked) + Op.STOP, - code_outputs=NON_RETURNING_SECTION, max_stack_height=1 + len(suffix), ) ] ) - - pre = { - TestAddress: Account( - balance=1000000000000000000000, - nonce=1, - ), - calling_contract_address: Account( - code=caller_contract, - nonce=1, - ), - destination_contract_address: Account( - code=Container( - sections=[ - Section.Code( - code=Op.MSTORE8(0, int.from_bytes(value_returndata_magic, "big")) - + Op.RETURN(0, 32), - code_outputs=NON_RETURNING_SECTION, - max_stack_height=2, - ) - ] - ), - nonce=1, - ), - } + calling_contract_address = pre.deploy_contract(caller_contract) tx = Transaction( - nonce=1, + sender=sender, to=Address(calling_contract_address), gas_limit=50000000, gas_price=10, @@ -394,13 +348,15 @@ def test_eof_calls_eof_mstore( ) def test_eof_calls_legacy_sstore( state_test: StateTestFiller, + pre: Alloc, + sender: EOA, opcode: Op, suffix: list[int], ): """Test EOF contracts calling Legacy contracts that use SSTORE""" env = Environment() - calling_contract_address = Address(0x1000000) - destination_contract_address = Address(0x1000001) + destination_contract_code = Op.SSTORE(slot_caller, Op.CALLER()) + Op.STOP + destination_contract_address = pre.deploy_contract(destination_contract_code) caller_contract = Container( sections=[ @@ -408,29 +364,14 @@ def test_eof_calls_legacy_sstore( code=Op.SSTORE(slot_call_result, opcode(destination_contract_address, *suffix)) + Op.SSTORE(slot_code_worked, value_code_worked) + Op.STOP, - code_outputs=NON_RETURNING_SECTION, max_stack_height=1 + len(suffix), ) ] ) - - pre = { - TestAddress: Account( - balance=1000000000000000000000, - nonce=1, - ), - calling_contract_address: Account( - code=caller_contract, - nonce=1, - ), - destination_contract_address: Account( - code=Op.SSTORE(slot_caller, Op.CALLER()) + Op.STOP, - nonce=1, - ), - } + calling_contract_address = pre.deploy_contract(caller_contract) tx = Transaction( - nonce=1, + sender=sender, to=Address(calling_contract_address), gas_limit=50000000, gas_price=10, @@ -476,13 +417,17 @@ def test_eof_calls_legacy_sstore( ) def test_eof_calls_legacy_mstore( state_test: StateTestFiller, + pre: Alloc, + sender: EOA, opcode: Op, suffix: list[int], ): """Test EOF contracts calling Legacy contracts that return data""" env = Environment() - calling_contract_address = Address(0x1000000) - destination_contract_address = Address(0x1000001) + destination_contract_code = Op.MSTORE8( + 0, int.from_bytes(value_returndata_magic, "big") + ) + Op.RETURN(0, 32) + destination_contract_address = pre.deploy_contract(destination_contract_code) caller_contract = Container( sections=[ @@ -492,29 +437,14 @@ def test_eof_calls_legacy_mstore( + Op.SSTORE(slot_returndata, Op.RETURNDATALOAD(0)) + Op.SSTORE(slot_code_worked, value_code_worked) + Op.STOP, - code_outputs=NON_RETURNING_SECTION, max_stack_height=1 + len(suffix), ) ] ) - - pre = { - TestAddress: Account( - balance=1000000000000000000000, - nonce=1, - ), - calling_contract_address: Account( - code=caller_contract, - nonce=1, - ), - destination_contract_address: Account( - code=Op.MSTORE8(0, int.from_bytes(value_returndata_magic, "big")) + Op.RETURN(0, 32), - nonce=1, - ), - } + calling_contract_address = pre.deploy_contract(caller_contract) tx = Transaction( - nonce=1, + sender=sender, to=Address(calling_contract_address), gas_limit=50000000, gas_price=10, diff --git a/tests/prague/eip7692_eof_v1/eip3540_eof_v1/test_code_validation.py b/tests/prague/eip7692_eof_v1/eip3540_eof_v1/test_code_validation.py index 95bedf716f..127dd46985 100644 --- a/tests/prague/eip7692_eof_v1/eip3540_eof_v1/test_code_validation.py +++ b/tests/prague/eip7692_eof_v1/eip3540_eof_v1/test_code_validation.py @@ -7,12 +7,12 @@ import pytest from ethereum_test_tools import ( + EOA, Account, Address, Alloc, Environment, EOFTestFiller, - TestAddress, Transaction, compute_eofcreate_address, ) @@ -50,50 +50,36 @@ def env(): # noqa: D103 @pytest.fixture -def create3_init_container(container: Container) -> Initcode: # noqa: D103 - return Initcode(deploy_container=container) +def sender(pre: Alloc): # noqa: D103 + return pre.fund_eoa() @pytest.fixture -def create3_opcode_contract_address() -> Address: # noqa: D103 - return Address(0x300) +def create3_init_container(container: Container) -> Initcode: # noqa: D103 + return Initcode(deploy_container=container) @pytest.fixture -def pre( # noqa: D103 - create3_opcode_contract_address: Address, +def create3_opcode_contract_address( # noqa: D103 + pre: Alloc, create3_init_container: Initcode, -) -> Alloc: - return Alloc( - { - TestAddress: Account( - balance=1000000000000000000000, - nonce=0, - ), - create3_opcode_contract_address: Account( - code=create3_init_container, - ), - } - ) +) -> Address: + return pre.deploy_contract(create3_init_container, address=Address(0x300)) @pytest.fixture def txs( # noqa: D103 - create3_opcode_contract_address: str, + sender: EOA, + create3_opcode_contract_address: Address, ) -> List[Transaction]: return [ Transaction( - nonce=nonce, - to=address, + to=create3_opcode_contract_address, gas_limit=100000000, gas_price=10, # data=initcode, protected=False, - ) - for nonce, address in enumerate( - [ - create3_opcode_contract_address, - ] + sender=sender, ) ] @@ -102,7 +88,7 @@ def txs( # noqa: D103 def post( # noqa: D103 create3_init_container: Initcode, container: Container, - create3_opcode_contract_address: str, + create3_opcode_contract_address: Address, ) -> Dict[Address, Account]: create_opcode_created_contract_address = compute_eofcreate_address( create3_opcode_contract_address, @@ -170,108 +156,3 @@ def test_legacy_initcode_invalid_eof_v1_contract( data=bytes(container), expect_exception=container.validity_error, ) - - -""" -@test_from(EOF_FORK_NAME) -def test_legacy_initcode_invalid_eof_v1_contract(_): - Test creating various types of invalid EOF V1 contracts using legacy - initcode, a contract creating transaction, - and the CREATE opcode. - tx_created_contract_address = compute_create_address(TestAddress, 0) - create_opcode_created_contract_address = compute_create_address(0x100, 0) - - env = Environment() - - pre = { - TestAddress: Account( - balance=1000000000000000000000, - nonce=0, - ), - Address(0x100): Account( - code=create_initcode_from_calldata, - ), - Address(0x200): Account( - code=create2_initcode_from_calldata, - ), - } - - post = { - Address(0x100): Account( - storage={ - 0: 1, - } - ), - tx_created_contract_address: Account.NONEXISTENT, - create_opcode_created_contract_address: Account.NONEXISTENT, - } - - tx_create_contract = Transaction( - nonce=0, - to=None, - gas_limit=100000000, - gas_price=10, - protected=False, - ) - tx_create_opcode = Transaction( - nonce=1, - to=Address(0x100), - gas_limit=100000000, - gas_price=10, - protected=False, - ) - tx_create2_opcode = Transaction( - nonce=2, - to=Address(0x200), - gas_limit=100000000, - gas_price=10, - protected=False, - ) - - for container in ALL_INVALID: - # print(container.name + ": " + bytes(container).hex()) - if container.validity_error == "": - print( - "WARN: Invalid container " - + f"`{container.name}` without validity error" - ) - legacy_initcode = Initcode(deploy_code=container) - tx_create_contract.data = legacy_initcode - tx_create_opcode.data = legacy_initcode - tx_create2_opcode.data = legacy_initcode - create2_opcode_created_contract_address = compute_create2_address( - 0x200, 0, bytes(legacy_initcode) - ) - post[create2_opcode_created_contract_address] = Account.NONEXISTENT - yield StateTest( - env=env, - pre=pre, - post=post, - txs=[ - tx_create_contract, - tx_create_opcode, - tx_create2_opcode, - ], - name=container.name - if container.name is not None - else "unknown_container", - ) - del post[create2_opcode_created_contract_address] - """ - - -# TODO: EOF cannot create legacy code: -# Tx -> EOF-initcode -> Legacy return (Fail) -# EOF contract CREATE -> EOF-initcode -> Legacy return (Fail) -# EOF contract CREATE2 -> EOF-initcode -> Legacy return (Fail) -# -# Tx -> Legacy-initcode -> Legacy return (Pass) -# EOF contract CREATE -> Legacy-initcode -> Legacy return (Fail) -# EOF contract CREATE2 -> Legacy-initcode -> Legacy return (Fail) -# -# Tx -> Legacy-initcode -> EOF return (Pass) -# EOF contract CREATE -> Legacy-initcode -> EOF return (Fail) -# EOF contract CREATE2 -> Legacy-initcode -> EOF return (Fail) -# TODO: Create empty contract from EOF -# TODO: No new opcodes in legacy code -# TODO: General EOF initcode validation diff --git a/tests/prague/eip7692_eof_v1/eip3540_eof_v1/test_execution_function.py b/tests/prague/eip7692_eof_v1/eip3540_eof_v1/test_execution_function.py index e34645a2eb..4f5b7a9e26 100644 --- a/tests/prague/eip7692_eof_v1/eip3540_eof_v1/test_execution_function.py +++ b/tests/prague/eip7692_eof_v1/eip3540_eof_v1/test_execution_function.py @@ -6,14 +6,7 @@ import pytest -from ethereum_test_tools import ( - Account, - Address, - Environment, - StateTestFiller, - TestAddress, - Transaction, -) +from ethereum_test_tools import Account, Alloc, Environment, StateTestFiller, Transaction from ethereum_test_tools.eof.v1 import Container, Section from ethereum_test_tools.eof.v1.constants import MAX_CODE_SECTIONS, MAX_RETURN_STACK_HEIGHT from ethereum_test_tools.vm.opcode import Opcodes as Op @@ -312,13 +305,7 @@ These contracts have a valid EOF V1 container format but fail when executed. """ -VALID: List[Container] = ( - CALL_SUCCEED_CONTRACTS - + CALL_FAIL_CONTRACTS - + [ - contract_call_within_deep_nested_callf, - ] -) +VALID: List[Container] = CALL_SUCCEED_CONTRACTS + CALL_FAIL_CONTRACTS """ List of all EOF V1 Containers used during execution tests. """ @@ -327,6 +314,7 @@ @pytest.mark.parametrize("container", CALL_SUCCEED_CONTRACTS, ids=lambda x: x.name) def test_eof_functions_contract_call_succeed( state_test: StateTestFiller, + pre: Alloc, container: Container, ): """ @@ -334,33 +322,21 @@ def test_eof_functions_contract_call_succeed( """ env = Environment() - caller_contract = Op.SSTORE(0, Op.CALL(Op.GAS, 0x200, 0, 0, 0, 0, 0)) + Op.STOP() - - pre = { - TestAddress: Account( - balance=1000000000000000000000, - nonce=1, - ), - Address(0x100): Account( - code=caller_contract, - nonce=1, - ), - Address(0x200): Account( - code=container, - nonce=1, - ), - } + sender = pre.fund_eoa() + container_address = pre.deploy_contract(container) + caller_contract = Op.SSTORE(0, Op.CALL(Op.GAS, container_address, 0, 0, 0, 0, 0)) + Op.STOP() + caller_address = pre.deploy_contract(caller_contract) tx = Transaction( - nonce=1, - to=Address(0x100), + to=caller_address, gas_limit=50000000, gas_price=10, protected=False, data="", + sender=sender, ) - post = {Address(0x100): Account(storage={0: 1})} + post = {caller_address: Account(storage={0: 1})} state_test( env=env, @@ -373,6 +349,7 @@ def test_eof_functions_contract_call_succeed( @pytest.mark.parametrize("container", CALL_FAIL_CONTRACTS, ids=lambda x: x.name) def test_eof_functions_contract_call_fail( state_test: StateTestFiller, + pre: Alloc, container: Container, ): """ @@ -380,33 +357,21 @@ def test_eof_functions_contract_call_fail( """ env = Environment() - caller_contract = Op.SSTORE(Op.CALL(Op.GAS, 0x200, 0, 0, 0, 0, 0), 1) + Op.STOP() - - pre = { - TestAddress: Account( - balance=1000000000000000000000, - nonce=1, - ), - Address(0x100): Account( - code=caller_contract, - nonce=1, - ), - Address(0x200): Account( - code=container, - nonce=1, - ), - } + sender = pre.fund_eoa() + container_address = pre.deploy_contract(container) + caller_contract = Op.SSTORE(Op.CALL(Op.GAS, container_address, 0, 0, 0, 0, 0), 1) + Op.STOP() + caller_address = pre.deploy_contract(caller_contract) tx = Transaction( - nonce=1, - to=Address(0x100), + to=caller_address, gas_limit=50000000, gas_price=10, protected=False, data="", + sender=sender, ) - post = {Address(0x100): Account(storage={0: 1})} + post = {caller_address: Account(storage={0: 1})} state_test( env=env, @@ -416,40 +381,66 @@ def test_eof_functions_contract_call_fail( ) -@pytest.mark.parametrize("container", CALL_FAIL_CONTRACTS, ids=lambda x: x.name) def test_eof_functions_contract_call_within_deep_nested( state_test: StateTestFiller, - container: Container, + pre: Alloc, ): """ Test performing a call within a nested callf and verify correct behavior of return stack in calling contract. + + TODO: This test belongs in EIP-7069 test folder, not code validation. """ env = Environment() - pre = { - TestAddress: Account( - balance=1000000000000000000000, - nonce=1, - ), - Address(0x100): Account( - code=contract_call_within_deep_nested_callf, - ), - Address(0x200): Account( - code=Op.SSTORE(0, 1) + Op.STOP(), - ), - } + nested_callee_address = pre.deploy_contract(code=Op.SSTORE(0, 1) + Op.STOP()) + contract_call_within_deep_nested_callf = Container( + name="contract_call_within_deep_nested_callf", + sections=[ + Section.Code( + code=Op.CALLF[1] + Op.SSTORE(0, 1) + Op.STOP, + max_stack_height=2, + ) + ] + + [ + # All sections call next section and on return, store a 1 + # to their call stack height key + Section.Code( + code=(Op.CALLF[i] + Op.SSTORE(i - 1, 1) + Op.RETF), + code_outputs=0, + max_stack_height=2, + ) + for i in range(2, MAX_CODE_SECTIONS) + ] + + [ + # Last section makes external contract call + Section.Code( + code=( + Op.EXTCALL(nested_callee_address, 0, 0, 0) + + Op.ISZERO + + Op.PUSH2(MAX_CODE_SECTIONS - 1) + + Op.SSTORE + + Op.RETF + ), + code_outputs=0, + max_stack_height=4, + ) + ], + ) + callee_address = pre.deploy_contract(contract_call_within_deep_nested_callf) + sender = pre.fund_eoa() + tx = Transaction( - nonce=1, - to=Address(0x100), + to=callee_address, gas_limit=50000000, gas_price=10, protected=False, data="", + sender=sender, ) post = { - Address(0x100): Account(storage={i: 1 for i in range(MAX_CODE_SECTIONS)}), - Address(0x200): Account( + callee_address: Account(storage={i: 1 for i in range(MAX_CODE_SECTIONS)}), + nested_callee_address: Account( storage={ 0: 1, } diff --git a/tests/prague/eip7692_eof_v1/eip3540_eof_v1/test_extcode.py b/tests/prague/eip7692_eof_v1/eip3540_eof_v1/test_extcode.py index 1b08c98ae7..06ff988224 100644 --- a/tests/prague/eip7692_eof_v1/eip3540_eof_v1/test_extcode.py +++ b/tests/prague/eip7692_eof_v1/eip3540_eof_v1/test_extcode.py @@ -4,17 +4,8 @@ import pytest from ethereum.crypto.hash import keccak256 -from ethereum_test_tools import ( - Account, - Address, - Environment, - StateTestFiller, - Storage, - TestAddress, - Transaction, -) +from ethereum_test_tools import Account, Alloc, Environment, StateTestFiller, Storage, Transaction from ethereum_test_tools.eof.v1 import Container, Section -from ethereum_test_tools.eof.v1.constants import NON_RETURNING_SECTION from ethereum_test_tools.vm.opcode import Opcodes as Op from .. import EOF_FORK_NAME @@ -27,62 +18,50 @@ def test_legacy_calls_eof_sstore( state_test: StateTestFiller, + pre: Alloc, ): """Test EXTCODE* opcodes calling EOF and legacy contracts""" env = Environment() - address_eof_contract = Address(0x1000000) - address_legacy_contract = Address(0x1000001) - address_test_contract = Address(0x1000002) + address_eof_contract = pre.deploy_contract( + Container( + sections=[ + Section.Code( + code=Op.RJUMP[0] + Op.STOP, + ) + ] + ) + ) + legacy_code = Op.PUSH1(2) + Op.JUMPDEST + Op.STOP + address_legacy_contract = pre.deploy_contract(legacy_code) + storage_test = Storage() + test_contract_code = ( + Op.SSTORE(storage_test.store_next(4), Op.EXTCODESIZE(address_legacy_contract)) + + Op.EXTCODECOPY(address_legacy_contract, 0, 0, Op.EXTCODESIZE(address_legacy_contract)) + + Op.SSTORE( + storage_test.store_next(legacy_code + (b"\0" * (32 - len(legacy_code)))), + Op.MLOAD(0), + ) + + Op.SSTORE( + storage_test.store_next(keccak256(legacy_code)), + Op.EXTCODEHASH(address_legacy_contract), + ) + + Op.SSTORE(storage_test.store_next(2), Op.EXTCODESIZE(address_eof_contract)) + + Op.EXTCODECOPY(address_eof_contract, 0x20, 0, Op.EXTCODESIZE(address_eof_contract)) + + Op.SSTORE(storage_test.store_next(b"\xef" + (b"\0" * 31)), Op.MLOAD(0x20)) + + Op.SSTORE( + storage_test.store_next(keccak256(b"\xef\x00")), + Op.EXTCODEHASH(address_eof_contract), + ) + ) + address_test_contract = pre.deploy_contract(test_contract_code) - legacy_code = Op.PUSH1(2) + Op.JUMPDEST + Op.STOP - pre = { - TestAddress: Account( - balance=1000000000000000000000, - nonce=1, - ), - address_eof_contract: Account( - code=Container( - sections=[ - Section.Code( - code=Op.RJUMP[0] + Op.STOP, - code_outputs=NON_RETURNING_SECTION, - ) - ] - ), - nonce=1, - ), - address_legacy_contract: Account( - code=legacy_code, - nonce=1, - ), - address_test_contract: Account( - code=Op.SSTORE(storage_test.store_next(4), Op.EXTCODESIZE(address_legacy_contract)) - + Op.EXTCODECOPY( - address_legacy_contract, 0, 0, Op.EXTCODESIZE(address_legacy_contract) - ) - + Op.SSTORE( - storage_test.store_next(legacy_code + (b"\0" * (32 - len(legacy_code)))), - Op.MLOAD(0), - ) - + Op.SSTORE( - storage_test.store_next(keccak256(legacy_code)), - Op.EXTCODEHASH(address_legacy_contract), - ) - + Op.SSTORE(storage_test.store_next(2), Op.EXTCODESIZE(address_eof_contract)) - + Op.EXTCODECOPY(address_eof_contract, 0x20, 0, Op.EXTCODESIZE(address_eof_contract)) - + Op.SSTORE(storage_test.store_next(b"\xef" + (b"\0" * 31)), Op.MLOAD(0x20)) - + Op.SSTORE( - storage_test.store_next(keccak256(b"\xef\x00")), - Op.EXTCODEHASH(address_eof_contract), - ) - ), - } + sender = pre.fund_eoa() tx = Transaction( - nonce=1, - to=Address(address_test_contract), - gas_limit=50000000, + sender=sender, + to=address_test_contract, + gas_limit=50_000_000, gas_price=10, protected=False, data="", diff --git a/tests/prague/eip7692_eof_v1/eip3540_eof_v1/test_section_order.py b/tests/prague/eip7692_eof_v1/eip3540_eof_v1/test_section_order.py new file mode 100644 index 0000000000..3f9cb32c84 --- /dev/null +++ b/tests/prague/eip7692_eof_v1/eip3540_eof_v1/test_section_order.py @@ -0,0 +1,221 @@ +""" +Different variations of EOF sections displacement +""" + +from enum import Enum +from typing import List + +import pytest + +from ethereum_test_tools import EOFTestFiller +from ethereum_test_tools import Opcodes as Op +from ethereum_test_tools.eof.v1 import ( + AutoSection, + Bytes, + Container, + EOFException, + Section, + SectionKind, +) +from ethereum_test_tools.eof.v1.constants import NON_RETURNING_SECTION + +from .. import EOF_FORK_NAME + +REFERENCE_SPEC_GIT_PATH = "EIPS/eip-3540.md" +REFERENCE_SPEC_VERSION = "8dcb0a8c1c0102c87224308028632cc986a61183" + +pytestmark = pytest.mark.valid_from(EOF_FORK_NAME) + + +class SectionTest(Enum): + """ + Enum for the test type + """ + + MISSING = 1 + WRONG_ORDER = 2 + + +class CasePosition(Enum): + """ + Enum for the test position + """ + + BODY = 1 + HEADER = 2 + BODY_AND_HEADER = 3 + + +def get_expected_code_exception( + section_kind, section_test, test_position +) -> tuple[str, EOFException | None]: + """ + Verification vectors with code and exception based on test combinations + """ + match (section_kind, section_test, test_position): + case (SectionKind.TYPE, SectionTest.MISSING, CasePosition.HEADER): + return "ef000102000100030400010000800001305000ef", EOFException.MISSING_TYPE_HEADER + case (SectionKind.TYPE, SectionTest.MISSING, CasePosition.BODY): + return ( + "ef0001010004020001000304000100305000ef", + EOFException.INVALID_SECTION_BODIES_SIZE, + ) + case (SectionKind.TYPE, SectionTest.MISSING, CasePosition.BODY_AND_HEADER): + return "ef0001020001000304000100305000ef", EOFException.MISSING_TYPE_HEADER + case (SectionKind.TYPE, SectionTest.WRONG_ORDER, CasePosition.HEADER): + return ( + "ef000102000100030100040400010000800001305000ef", + EOFException.MISSING_TYPE_HEADER, + ) + case (SectionKind.TYPE, SectionTest.WRONG_ORDER, CasePosition.BODY): + return ( + "ef000101000402000100030400010030500000800001ef", + # TODO why invalid first section type? it should say that the body incorrect + EOFException.INVALID_FIRST_SECTION_TYPE, + ) + case (SectionKind.TYPE, SectionTest.WRONG_ORDER, CasePosition.BODY_AND_HEADER): + return ( + "ef000102000100030100040400010030500000800001ef", + EOFException.MISSING_TYPE_HEADER, + ) + case (SectionKind.CODE, SectionTest.MISSING, CasePosition.HEADER): + return "ef00010100040400010000800001305000ef", EOFException.MISSING_CODE_HEADER + case (SectionKind.CODE, SectionTest.MISSING, CasePosition.BODY): + return ( + "ef000101000402000100030400010000800001ef", + # TODO should be an exception of empty code bytes, because it can understand that + # last byte is data section byte + EOFException.INVALID_SECTION_BODIES_SIZE, + ) + case (SectionKind.CODE, SectionTest.MISSING, CasePosition.BODY_AND_HEADER): + return "ef00010100040400010000800001ef", EOFException.MISSING_CODE_HEADER + case (SectionKind.CODE, SectionTest.WRONG_ORDER, CasePosition.HEADER): + return ( + "ef000101000404000102000100030000800001305000ef", + EOFException.MISSING_CODE_HEADER, + ) + case (SectionKind.CODE, SectionTest.WRONG_ORDER, CasePosition.BODY): + return ( + "ef000101000402000100030400010000800001ef305000", + EOFException.UNDEFINED_INSTRUCTION, + ) + case (SectionKind.CODE, SectionTest.WRONG_ORDER, CasePosition.BODY_AND_HEADER): + return ( + "ef000101000404000102000100030000800001ef305000", + EOFException.MISSING_CODE_HEADER, + ) + case (SectionKind.DATA, SectionTest.MISSING, CasePosition.HEADER): + return "ef000101000402000100030000800001305000ef", EOFException.MISSING_DATA_SECTION + case (SectionKind.DATA, SectionTest.MISSING, CasePosition.BODY): + return ( + "ef000101000402000100030400010000800001305000", + EOFException.TOPLEVEL_CONTAINER_TRUNCATED, + ) + case (SectionKind.DATA, SectionTest.MISSING, CasePosition.BODY_AND_HEADER): + return "ef000101000402000100030000800001305000", EOFException.MISSING_DATA_SECTION + case (SectionKind.DATA, SectionTest.WRONG_ORDER, CasePosition.HEADER): + return ( + "ef000104000101000402000100030000800001305000ef", + EOFException.MISSING_TYPE_HEADER, + ) + case (SectionKind.DATA, SectionTest.WRONG_ORDER, CasePosition.BODY): + return ( + "ef0001010004020001000304000100ef00800001305000", + EOFException.INVALID_FIRST_SECTION_TYPE, + ) + case (SectionKind.DATA, SectionTest.WRONG_ORDER, CasePosition.BODY_AND_HEADER): + return ( + "ef0001040001010004020001000300ef00800001305000", + EOFException.MISSING_TYPE_HEADER, + ) + return "", None + + +@pytest.mark.parametrize("section_kind", [SectionKind.TYPE, SectionKind.CODE, SectionKind.DATA]) +@pytest.mark.parametrize("section_test", [SectionTest.MISSING, SectionTest.WRONG_ORDER]) +@pytest.mark.parametrize( + "test_position", [CasePosition.BODY, CasePosition.HEADER, CasePosition.BODY_AND_HEADER] +) +def test_section_order( + eof_test: EOFTestFiller, + section_kind: SectionKind, + section_test: SectionTest, + test_position: CasePosition, +): + """ + Test sections order and it appearance in body and header + """ + + def calculate_skip_flag(kind, position) -> bool: + return ( + False + if (section_kind != kind) + else ( + True + if section_test == SectionTest.MISSING + and (test_position == position or test_position == CasePosition.BODY_AND_HEADER) + else False + ) + ) + + def make_section_order(kind) -> List[Section]: + if section_test != SectionTest.WRONG_ORDER: + return [section_type, section_code, section_data] + if kind == SectionKind.TYPE: + return [section_code, section_type, section_data] + if kind == SectionKind.CODE: + return [section_type, section_data, section_code] + if kind == SectionKind.DATA: + return [section_data, section_type, section_code] + return [section_type, section_code, section_data] + + section_code = Section.Code( + code=Op.ADDRESS + Op.POP + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + skip_header_listing=calculate_skip_flag(SectionKind.CODE, CasePosition.HEADER), + skip_body_listing=calculate_skip_flag(SectionKind.CODE, CasePosition.BODY), + ) + section_type = Section( + kind=SectionKind.TYPE, + data=Bytes.fromhex("00800001"), + custom_size=4, + skip_header_listing=calculate_skip_flag(SectionKind.TYPE, CasePosition.HEADER), + skip_body_listing=calculate_skip_flag(SectionKind.TYPE, CasePosition.BODY), + ) + section_data = Section.Data( + "ef", + skip_header_listing=calculate_skip_flag(SectionKind.DATA, CasePosition.HEADER), + skip_body_listing=calculate_skip_flag(SectionKind.DATA, CasePosition.BODY), + ) + + eof_code = Container( + sections=make_section_order(section_kind), + auto_type_section=AutoSection.NONE, + auto_sort_sections=( + AutoSection.AUTO + if section_test != SectionTest.WRONG_ORDER + else ( + AutoSection.ONLY_BODY + if test_position == CasePosition.HEADER + else ( + AutoSection.ONLY_HEADER + if test_position == CasePosition.BODY + else AutoSection.NONE + ) + ) + ), + ) + + expected_code, expected_exception = get_expected_code_exception( + section_kind, section_test, test_position + ) + + # TODO remove this after Container class implementation is reliable + assert bytes(eof_code).hex() == bytes.fromhex(expected_code).hex() + + eof_test( + data=eof_code, + expect_exception=expected_exception, + ) diff --git a/tests/prague/eip7692_eof_v1/eip4200_relative_jumps/test_rjumpi.py b/tests/prague/eip7692_eof_v1/eip4200_relative_jumps/test_rjumpi.py index 869dd794da..d1f07503ea 100644 --- a/tests/prague/eip7692_eof_v1/eip4200_relative_jumps/test_rjumpi.py +++ b/tests/prague/eip7692_eof_v1/eip4200_relative_jumps/test_rjumpi.py @@ -6,12 +6,12 @@ from ethereum_test_tools import ( Account, + Alloc, Environment, EOFException, EOFStateTestFiller, EOFTestFiller, StateTestFiller, - TestAddress, Transaction, ) from ethereum_test_tools.eof.v1 import Container, Section @@ -39,37 +39,36 @@ ) def test_rjumpi_condition_forwards( state_test: StateTestFiller, + pre: Alloc, calldata: bytes, ): """Test RJUMPI contract switching based on external input""" env = Environment() + sender = pre.fund_eoa(10**18) + contract_address = pre.deploy_contract( + code=Container( + sections=[ + Section.Code( + code=Op.PUSH1(0) + + Op.CALLDATALOAD + + Op.RJUMPI[6] + + Op.SSTORE(slot_conditional_result, value_calldata_false) + + Op.STOP + + Op.SSTORE(slot_conditional_result, value_calldata_true) + + Op.STOP, + max_stack_height=2, + ) + ] + ), + ) tx = Transaction( - nonce=1, + to=contract_address, gas_limit=10_000_000, data=calldata, + sender=sender, ) - pre = { - TestAddress: Account(balance=10**18, nonce=tx.nonce), - tx.to: Account( - code=Container( - sections=[ - Section.Code( - code=Op.PUSH1(0) - + Op.CALLDATALOAD - + Op.RJUMPI[6] - + Op.SSTORE(slot_conditional_result, value_calldata_false) - + Op.STOP - + Op.SSTORE(slot_conditional_result, value_calldata_true) - + Op.STOP, - max_stack_height=2, - ) - ] - ), - nonce=1, - ), - } post = { - tx.to: Account( + contract_address: Account( storage={ slot_conditional_result: value_calldata_false if calldata == b"\0" @@ -86,39 +85,38 @@ def test_rjumpi_condition_forwards( ) def test_rjumpi_condition_backwards( state_test: StateTestFiller, + pre: Alloc, calldata: bytes, ): """Test RJUMPI contract switching based on external input""" env = Environment() + sender = pre.fund_eoa(10**18) + contract_address = pre.deploy_contract( + code=Container( + sections=[ + Section.Code( + code=Op.PUSH1(1) + + Op.RJUMPI[6] + + Op.SSTORE(slot_conditional_result, value_calldata_true) + + Op.STOP + + Op.PUSH0 + + Op.CALLDATALOAD + + Op.RJUMPI[-11] + + Op.SSTORE(slot_conditional_result, value_calldata_false) + + Op.STOP, + max_stack_height=2, + ) + ] + ) + ) tx = Transaction( - nonce=1, + to=contract_address, gas_limit=10_000_000, data=calldata, + sender=sender, ) - pre = { - TestAddress: Account(balance=10**18, nonce=tx.nonce), - tx.to: Account( - code=Container( - sections=[ - Section.Code( - code=Op.PUSH1(1) - + Op.RJUMPI[6] - + Op.SSTORE(slot_conditional_result, value_calldata_true) - + Op.STOP - + Op.PUSH0 - + Op.CALLDATALOAD - + Op.RJUMPI[-11] - + Op.SSTORE(slot_conditional_result, value_calldata_false) - + Op.STOP, - max_stack_height=2, - ) - ] - ), - nonce=1, - ), - } post = { - tx.to: Account( + contract_address: Account( storage={ slot_conditional_result: value_calldata_false if calldata == b"\0" @@ -135,34 +133,33 @@ def test_rjumpi_condition_backwards( ) def test_rjumpi_condition_zero( state_test: StateTestFiller, + pre: Alloc, calldata: bytes, ): """Test RJUMPI contract switching based on external input""" env = Environment() + sender = pre.fund_eoa(10**18) + contract_address = pre.deploy_contract( + code=Container( + sections=[ + Section.Code( + code=Op.PUSH0 + + Op.CALLDATALOAD + + Op.RJUMPI[0] + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP, + max_stack_height=2, + ) + ] + ), + ) tx = Transaction( - nonce=1, + to=contract_address, gas_limit=10_000_000, data=calldata, + sender=sender, ) - pre = { - TestAddress: Account(balance=10**18, nonce=tx.nonce), - tx.to: Account( - code=Container( - sections=[ - Section.Code( - code=Op.PUSH0 - + Op.CALLDATALOAD - + Op.RJUMPI[0] - + Op.SSTORE(slot_code_worked, value_code_worked) - + Op.STOP, - max_stack_height=2, - ) - ] - ), - nonce=1, - ), - } - post = {tx.to: Account(storage={slot_code_worked: value_code_worked})} + post = {contract_address: Account(storage={slot_code_worked: value_code_worked})} state_test(env=env, tx=tx, pre=pre, post=post) diff --git a/tests/prague/eip7692_eof_v1/eip663_dupn_swapn_exchange/conftest.py b/tests/prague/eip7692_eof_v1/eip663_dupn_swapn_exchange/conftest.py deleted file mode 100644 index 9c7b2de037..0000000000 --- a/tests/prague/eip7692_eof_v1/eip663_dupn_swapn_exchange/conftest.py +++ /dev/null @@ -1,14 +0,0 @@ -""" -Pytest fixtures for EIP-663 tests -""" -import pytest - -from ethereum_test_tools import Transaction - - -@pytest.fixture -def tx() -> Transaction: - """ - Produces the default Transaction. - """ - return Transaction(to=0xC0DE, gas_limit=10_000_000) diff --git a/tests/prague/eip7692_eof_v1/eip663_dupn_swapn_exchange/test_dupn.py b/tests/prague/eip7692_eof_v1/eip663_dupn_swapn_exchange/test_dupn.py index 07f6fe5472..e342d69e7b 100644 --- a/tests/prague/eip7692_eof_v1/eip663_dupn_swapn_exchange/test_dupn.py +++ b/tests/prague/eip7692_eof_v1/eip663_dupn_swapn_exchange/test_dupn.py @@ -5,15 +5,7 @@ import pytest -from ethereum_test_tools import ( - Account, - Environment, - EOFException, - EOFTestFiller, - StateTestFiller, - TestAddress, - Transaction, -) +from ethereum_test_tools import Account, EOFException, EOFStateTestFiller, EOFTestFiller from ethereum_test_tools.eof.v1 import Container, Section from ethereum_test_tools.eof.v1.constants import MAX_OPERAND_STACK_HEIGHT from ethereum_test_tools.vm.opcode import Opcodes as Op @@ -26,10 +18,7 @@ @pytest.mark.valid_from(EOF_FORK_NAME) -def test_dupn_all_valid_immediates( - tx: Transaction, - state_test: StateTestFiller, -): +def test_dupn_all_valid_immediates(eof_state_test: EOFStateTestFiller): """ Test case for all valid DUPN immediates. """ @@ -47,18 +36,12 @@ def test_dupn_all_valid_immediates( ], ) - pre = { - TestAddress: Account(balance=1_000_000_000), - tx.to: Account(code=eof_code), - } + post = Account(storage=dict(zip(range(0, n), reversed(values)))) - post = {tx.to: Account(storage=dict(zip(range(0, n), reversed(values))))} - - state_test( - env=Environment(), - pre=pre, - post=post, - tx=tx, + eof_state_test( + tx_sender_funding_amount=1_000_000_000, + data=eof_code, + container_post=post, ) diff --git a/tests/prague/eip7692_eof_v1/eip663_dupn_swapn_exchange/test_exchange.py b/tests/prague/eip7692_eof_v1/eip663_dupn_swapn_exchange/test_exchange.py index 559b8e9f84..14103f02c4 100644 --- a/tests/prague/eip7692_eof_v1/eip663_dupn_swapn_exchange/test_exchange.py +++ b/tests/prague/eip7692_eof_v1/eip663_dupn_swapn_exchange/test_exchange.py @@ -5,15 +5,7 @@ import pytest -from ethereum_test_tools import ( - Account, - Environment, - EOFException, - EOFTestFiller, - StateTestFiller, - TestAddress, - Transaction, -) +from ethereum_test_tools import Account, EOFException, EOFStateTestFiller, EOFTestFiller from ethereum_test_tools.eof.v1 import Container, Section from ethereum_test_tools.vm.opcode import Opcodes as Op @@ -25,10 +17,7 @@ @pytest.mark.valid_from(EOF_FORK_NAME) -def test_exchange_all_valid_immediates( - tx: Transaction, - state_test: StateTestFiller, -): +def test_exchange_all_valid_immediates(eof_state_test: EOFStateTestFiller): """ Test case for all valid EXCHANGE immediates. """ @@ -48,11 +37,6 @@ def test_exchange_all_valid_immediates( ], ) - pre = { - TestAddress: Account(balance=1_000_000_000), - tx.to: Account(code=eof_code), - } - # this does the same full-loop exchange values_rotated = list(range(0x3E8, 0x3E8 + s)) for e in range(0, n): @@ -62,13 +46,12 @@ def test_exchange_all_valid_immediates( values_rotated[a] = values_rotated[b] values_rotated[b] = temp - post = {tx.to: Account(storage=dict(zip(range(0, s), reversed(values_rotated))))} + post = Account(storage=dict(zip(range(0, s), reversed(values_rotated)))) - state_test( - env=Environment(), - pre=pre, - post=post, - tx=tx, + eof_state_test( + tx_sender_funding_amount=1_000_000_000, + data=eof_code, + container_post=post, ) diff --git a/tests/prague/eip7692_eof_v1/eip663_dupn_swapn_exchange/test_swapn.py b/tests/prague/eip7692_eof_v1/eip663_dupn_swapn_exchange/test_swapn.py index cfa175a5bb..33e4de0f1f 100644 --- a/tests/prague/eip7692_eof_v1/eip663_dupn_swapn_exchange/test_swapn.py +++ b/tests/prague/eip7692_eof_v1/eip663_dupn_swapn_exchange/test_swapn.py @@ -5,15 +5,7 @@ import pytest -from ethereum_test_tools import ( - Account, - Environment, - EOFException, - EOFTestFiller, - StateTestFiller, - TestAddress, - Transaction, -) +from ethereum_test_tools import Account, EOFException, EOFStateTestFiller, EOFTestFiller from ethereum_test_tools.eof.v1 import Container, Section from ethereum_test_tools.eof.v1.constants import MAX_OPERAND_STACK_HEIGHT from ethereum_test_tools.vm.opcode import Opcodes as Op @@ -26,10 +18,7 @@ @pytest.mark.valid_from(EOF_FORK_NAME) -def test_swapn_all_valid_immediates( - tx: Transaction, - state_test: StateTestFiller, -): +def test_swapn_all_valid_immediates(eof_state_test: EOFStateTestFiller): """ Test case for all valid SWAPN immediates. """ @@ -47,19 +36,13 @@ def test_swapn_all_valid_immediates( ], ) - pre = { - TestAddress: Account(balance=1_000_000_000), - tx.to: Account(code=eof_code), - } - values_rotated = list(values[1:]) + [values[0]] - post = {tx.to: Account(storage=dict(zip(range(0, n), reversed(values_rotated))))} + post = Account(storage=dict(zip(range(0, n), reversed(values_rotated)))) - state_test( - env=Environment(), - pre=pre, - post=post, - tx=tx, + eof_state_test( + tx_sender_funding_amount=1_000_000_000, + data=eof_code, + container_post=post, ) diff --git a/tests/prague/eip7692_eof_v1/eip7069_extcall/test_address_space_extension.py b/tests/prague/eip7692_eof_v1/eip7069_extcall/test_address_space_extension.py index 188177291f..d2e192a26f 100644 --- a/tests/prague/eip7692_eof_v1/eip7069_extcall/test_address_space_extension.py +++ b/tests/prague/eip7692_eof_v1/eip7069_extcall/test_address_space_extension.py @@ -5,14 +5,7 @@ import pytest -from ethereum_test_tools import ( - Account, - Address, - Environment, - StateTestFiller, - TestAddress, - Transaction, -) +from ethereum_test_tools import Account, Address, Alloc, Environment, StateTestFiller, Transaction from ethereum_test_tools.eof.v1 import Container, Section from ethereum_test_tools.vm.opcode import Opcodes as Op @@ -24,10 +17,6 @@ pytestmark = pytest.mark.valid_from(EOF_FORK_NAME) -_address_allocation = itertools.count(0x10000) -address_entry_point = Address(next(_address_allocation)) -address_caller = Address(next(_address_allocation)) - _slot = itertools.count(1) slot_top_level_call_status = next(_slot) slot_target_call_status = next(_slot) @@ -50,7 +39,14 @@ ), ) @pytest.mark.parametrize( - "target_account_type", ("empty", "EOA", "LegacyContract", "EOFContract"), ids=lambda x: x + "target_account_type", + ( + "empty", + "EOA", + "LegacyContract", # Hard-codes an address in pre-alloc + "EOFContract", # Hard-codes an address in pre-alloc + ), + ids=lambda x: x, ) @pytest.mark.parametrize( "target_opcode", @@ -66,6 +62,7 @@ ) def test_address_space_extension( state_test: StateTestFiller, + pre: Alloc, target_address: bytes, target_opcode: Op, target_account_type: str, @@ -96,62 +93,57 @@ def test_address_space_extension( case _: raise ValueError("Unexpected opcode ", target_opcode) - pre = { - TestAddress: Account( - balance=1000000000000000000000, - nonce=1, - ), - address_entry_point: Account( - code=( - Op.MSTORE(0, Op.PUSH32(target_address)) - + Op.SSTORE( - slot_top_level_call_status, - Op.CALL(50000, address_caller, 0, 0, 32, 0, 0), - ) - + Op.STOP() - ), - nonce=1, - storage={ - slot_top_level_call_status: value_exceptional_abort_canary, - }, - ), - address_caller: Account( - code=Container( - sections=[ - Section.Code( - code=Op.SSTORE( - slot_target_call_status, - target_opcode(Op.CALLDATALOAD(0), *call_suffix), - ) - + Op.RETURNDATACOPY(0, 0, Op.RETURNDATASIZE) - + Op.SSTORE(slot_target_returndata, Op.MLOAD(0)) - + Op.STOP, - code_inputs=0, - max_stack_height=1 + len(call_suffix), + sender = pre.fund_eoa() + + address_caller = pre.deploy_contract( + Container( + sections=[ + Section.Code( + code=Op.SSTORE( + slot_target_call_status, + target_opcode(Op.CALLDATALOAD(0), *call_suffix), # type: ignore ) - ], - ) - if ase_ready_opcode - else Op.SSTORE( - slot_target_call_status, - target_opcode(Op.GAS, Op.CALLDATALOAD(0), *call_suffix), - ) - + Op.RETURNDATACOPY(0, 0, Op.RETURNDATASIZE) - + Op.SSTORE(slot_target_returndata, Op.MLOAD(0)) - + Op.STOP, - nonce=1, - storage={ - slot_target_call_status: value_exceptional_abort_canary, - slot_target_returndata: value_exceptional_abort_canary, - }, - ), - } + + Op.RETURNDATACOPY(0, 0, Op.RETURNDATASIZE) + + Op.SSTORE(slot_target_returndata, Op.MLOAD(0)) + + Op.STOP, + code_inputs=0, + max_stack_height=1 + len(call_suffix), + ) + ], + ) + if ase_ready_opcode + else Op.SSTORE( + slot_target_call_status, + target_opcode(Op.GAS, Op.CALLDATALOAD(0), *call_suffix), # type: ignore + ) + + Op.RETURNDATACOPY(0, 0, Op.RETURNDATASIZE) + + Op.SSTORE(slot_target_returndata, Op.MLOAD(0)) + + Op.STOP, + storage={ + slot_target_call_status: value_exceptional_abort_canary, + slot_target_returndata: value_exceptional_abort_canary, + }, + ) + + address_entry_point = pre.deploy_contract( + Op.MSTORE(0, Op.PUSH32(target_address)) + + Op.SSTORE( + slot_top_level_call_status, + Op.CALL(50000, address_caller, 0, 0, 32, 0, 0), + ) + + Op.STOP(), + storage={ + slot_top_level_call_status: value_exceptional_abort_canary, + }, + ) + match target_account_type: case "empty": # add no account pass case "EOA": - pre[Address(stripped_address)] = Account(code="", balance=10**18, nonce=9) + pre.fund_address(Address(stripped_address), 10**18) + # TODO: we could use pre.fund_eoa here with nonce!=0. case "LegacyContract": pre[Address(stripped_address)] = Account( code=Op.MSTORE(0, Op.ADDRESS) + Op.RETURN(0, 32), @@ -229,9 +221,9 @@ def test_address_space_extension( } tx = Transaction( - nonce=1, + sender=sender, to=address_entry_point, - gas_limit=50000000, + gas_limit=50_000_000, gas_price=10, protected=False, data="", diff --git a/tests/prague/eip7692_eof_v1/eip7069_extcall/test_returndataload.py b/tests/prague/eip7692_eof_v1/eip7069_extcall/test_returndataload.py index 51f423b106..388b95c3aa 100644 --- a/tests/prague/eip7692_eof_v1/eip7069_extcall/test_returndataload.py +++ b/tests/prague/eip7692_eof_v1/eip7069_extcall/test_returndataload.py @@ -6,16 +6,8 @@ import pytest -from ethereum_test_tools import ( - Account, - Address, - Environment, - StateTestFiller, - TestAddress, - Transaction, -) +from ethereum_test_tools import Account, Alloc, Environment, StateTestFiller, Transaction from ethereum_test_tools.eof.v1 import Container, Section -from ethereum_test_tools.eof.v1.constants import NON_RETURNING_SECTION from ethereum_test_tools.vm.opcode import Opcodes as Op from .. import EOF_FORK_NAME @@ -73,6 +65,7 @@ ) def test_returndatacopy_handling( state_test: StateTestFiller, + pre: Alloc, call_prefix: List[int], opcode: Op, call_suffix: List[int], @@ -91,60 +84,30 @@ def test_returndatacopy_handling( Entrypoint copies the test area to storage slots, and the expected result is asserted. """ env = Environment() - address_entry_point = Address(0x1000000) - address_caller = Address(0x1000001) - address_returner = Address(0x1000002) - tx = Transaction(to=address_entry_point, gas_limit=2_000_000, nonce=1) slot_result_start = 0x1000 - pre = { - TestAddress: Account(balance=10**18, nonce=tx.nonce), - address_entry_point: Account( - nonce=1, - code=Op.NOOP - # First, create a "dirty" area, so we can check zero overwrite - + Op.MSTORE(0x00, -1) + Op.MSTORE(0x20, -1) - # call the contract under test - + Op.DELEGATECALL(1_000_000, address_caller, 0, 0, 0, 0) - + Op.RETURNDATACOPY(0, 0, Op.RETURNDATASIZE) - # store the return data - + Op.SSTORE(slot_result_start, Op.MLOAD(0x0)) - + Op.SSTORE(slot_result_start + 1, Op.MLOAD(0x20)) - + Op.SSTORE(slot_code_worked, value_code_worked) - + Op.STOP, - ), - address_returner: Account( - nonce=1, - code=Container( - sections=[ - Section.Code( - code=Op.DATACOPY(0, 0, Op.DATASIZE) + Op.RETURN(0, Op.DATASIZE), - code_outputs=NON_RETURNING_SECTION, - max_stack_height=3, - ), - Section.Data(data=return_data), - ] - ), - ), - } + sender = pre.fund_eoa(10**18) + + address_returner = pre.deploy_contract( + Container( + sections=[ + Section.Code( + code=Op.DATACOPY(0, 0, Op.DATASIZE) + Op.RETURN(0, Op.DATASIZE), + max_stack_height=3, + ), + Section.Data(data=return_data), + ] + ) + ) result = [0xFF] * 0x40 result[0:size] = [0] * size extent = size - max(0, size + offset - len(return_data)) if extent > 0 and len(return_data) > 0: result[0:extent] = [return_data[0]] * extent - post = { - address_entry_point: Account( - storage={ - slot_code_worked: value_code_worked, - slot_result_start: bytes(result[:0x20]), - (slot_result_start + 0x1): bytes(result[0x20:]), - } - ) - } - code_under_test: bytes = ( + code_under_test = ( opcode(*call_prefix, address_returner, *call_suffix) + Op.RETURNDATACOPY(0, offset, size) + Op.SSTORE(slot_code_worked, value_code_worked) @@ -152,8 +115,8 @@ def test_returndatacopy_handling( ) match opcode: case Op.EXTCALL | Op.EXTDELEGATECALL | Op.EXTSTATICCALL: - pre[address_caller] = Account( - code=Container( + address_caller = pre.deploy_contract( + Container( sections=[ Section.Code( code=code_under_test, @@ -163,17 +126,44 @@ def test_returndatacopy_handling( ) ) case Op.CALL | Op.CALLCODE | Op.DELEGATECALL | Op.STATICCALL: - pre[address_caller] = Account( - code=code_under_test, - ) - if (offset + size) > len(return_data): - post[address_entry_point] = Account( - storage={ - slot_code_worked: value_code_worked, - slot_result_start: b"\xff" * 32, - slot_result_start + 1: b"\xff" * 32, - } - ) + address_caller = pre.deploy_contract(code_under_test) + + address_entry_point = pre.deploy_contract( + Op.NOOP + # First, create a "dirty" area, so we can check zero overwrite + + Op.MSTORE(0x00, -1) + + Op.MSTORE(0x20, -1) + # call the contract under test + + Op.DELEGATECALL(1_000_000, address_caller, 0, 0, 0, 0) + + Op.RETURNDATACOPY(0, 0, Op.RETURNDATASIZE) + # store the return data + + Op.SSTORE(slot_result_start, Op.MLOAD(0x0)) + + Op.SSTORE(slot_result_start + 1, Op.MLOAD(0x20)) + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP, + ) + + post = { + address_entry_point: Account( + storage={ + slot_code_worked: value_code_worked, + slot_result_start: bytes(result[:0x20]), + (slot_result_start + 0x1): bytes(result[0x20:]), + } + ) + } + if opcode in [Op.CALL, Op.CALLCODE, Op.DELEGATECALL, Op.STATICCALL] and ( + (offset + size) > len(return_data) + ): + post[address_entry_point] = Account( + storage={ + slot_code_worked: value_code_worked, + slot_result_start: b"\xff" * 32, + slot_result_start + 1: b"\xff" * 32, + } + ) + + tx = Transaction(to=address_entry_point, gas_limit=2_000_000, sender=sender) state_test( env=env, @@ -214,6 +204,7 @@ def test_returndatacopy_handling( ) def test_returndataload_handling( state_test: StateTestFiller, + pre: Alloc, opcode: Op, call_suffix: List[int], return_data: bytes, @@ -225,41 +216,34 @@ def test_returndataload_handling( The parameters offset and return data are configured to test boundary conditions. """ env = Environment() - address_entry_point = Address(0x1000000) - address_returner = Address(0x1000001) - tx = Transaction(to=address_entry_point, gas_limit=2_000_000, nonce=1) slot_result_start = 0x1000 - pre = { - TestAddress: Account(balance=10**18, nonce=tx.nonce), - address_entry_point: Account( - nonce=1, - code=Container( - sections=[ - Section.Code( - code=opcode(address_returner, *call_suffix) - + Op.SSTORE(slot_result_start, Op.RETURNDATALOAD(offset)) - + Op.SSTORE(slot_code_worked, value_code_worked) - + Op.STOP, - max_stack_height=len(call_suffix) + 1, - ) - ] - ), - ), - address_returner: Account( - nonce=1, - code=Container( - sections=[ - Section.Code( - code=Op.DATACOPY(0, 0, Op.DATASIZE) + Op.RETURN(0, Op.DATASIZE), - max_stack_height=3, - ), - Section.Data(data=return_data), - ] - ), - ), - } + sender = pre.fund_eoa(10**18) + address_returner = pre.deploy_contract( + Container( + sections=[ + Section.Code( + code=Op.DATACOPY(0, 0, Op.DATASIZE) + Op.RETURN(0, Op.DATASIZE), + max_stack_height=3, + ), + Section.Data(data=return_data), + ] + ) + ) + address_entry_point = pre.deploy_contract( + Container( + sections=[ + Section.Code( + code=opcode(address_returner, *call_suffix) + + Op.SSTORE(slot_result_start, Op.RETURNDATALOAD(offset)) + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP, + max_stack_height=len(call_suffix) + 1, + ) + ] + ) + ) result = [0] * 0x20 extent = 0x20 - max(0, 0x20 + offset - len(return_data)) @@ -274,6 +258,8 @@ def test_returndataload_handling( ) } + tx = Transaction(to=address_entry_point, gas_limit=2_000_000, sender=sender) + state_test( env=env, pre=pre, diff --git a/tests/prague/eip7692_eof_v1/eip7480_data_section/test_data_opcodes.py b/tests/prague/eip7692_eof_v1/eip7480_data_section/test_data_opcodes.py index af8c97cedc..75475781b2 100644 --- a/tests/prague/eip7692_eof_v1/eip7480_data_section/test_data_opcodes.py +++ b/tests/prague/eip7692_eof_v1/eip7480_data_section/test_data_opcodes.py @@ -4,14 +4,7 @@ import pytest -from ethereum_test_tools import ( - Account, - Address, - Environment, - StateTestFiller, - TestAddress, - Transaction, -) +from ethereum_test_tools import Account, Alloc, Environment, StateTestFiller, Transaction from ethereum_test_tools.eof.v1 import Container, Section from ethereum_test_tools.eof.v1.constants import MAX_CODE_SECTIONS from ethereum_test_tools.vm.opcode import Opcodes as Op @@ -154,6 +147,7 @@ def create_data_test(offset: int, datasize: int): ) def test_data_section_succeed( state_test: StateTestFiller, + pre: Alloc, offset: int, datasize: int, ): @@ -162,34 +156,23 @@ def test_data_section_succeed( """ env = Environment() - caller_contract = Op.SSTORE(0, Op.DELEGATECALL(Op.GAS, 0x200, 0, 0, 0, 0)) + Op.STOP() (container, expected_storage) = create_data_test(offset, datasize) - - pre = { - TestAddress: Account( - balance=1000000000000000000000, - nonce=1, - ), - Address(0x100): Account( - code=caller_contract, - nonce=1, - ), - Address(0x200): Account( - code=container, - nonce=1, - ), - } + callee_contract = pre.deploy_contract(code=container) + entry_point = pre.deploy_contract( + code=Op.SSTORE(0, Op.DELEGATECALL(Op.GAS, callee_contract, 0, 0, 0, 0)) + Op.STOP() + ) + sender = pre.fund_eoa() tx = Transaction( - nonce=1, - to=Address(0x100), + to=entry_point, gas_limit=50000000, gas_price=10, protected=False, data="", + sender=sender, ) - post = {Address(0x100): Account(storage=expected_storage)} + post = {entry_point: Account(storage=expected_storage)} state_test( env=env, diff --git a/tests/prague/eip7692_eof_v1/eip7620_eof_create/helpers.py b/tests/prague/eip7692_eof_v1/eip7620_eof_create/helpers.py index 5ca1d5baba..04ccca67bd 100644 --- a/tests/prague/eip7692_eof_v1/eip7620_eof_create/helpers.py +++ b/tests/prague/eip7692_eof_v1/eip7620_eof_create/helpers.py @@ -3,9 +3,7 @@ """ import itertools -from ethereum_test_tools import Address from ethereum_test_tools import Opcodes as Op -from ethereum_test_tools import Transaction from ethereum_test_tools.eof.v1 import Container, Section """Storage addresses for common testing fields""" @@ -44,40 +42,3 @@ Section.Container(container=smallest_runtime_subcontainer), ], ) - - -def fixed_address(index: int) -> Address: - """ - Returns an determinstic address for testing - Parameters - ---------- - index - how foar off of the initial to create the address - - Returns - ------- - An address, unique per index and human friendly for testing - - """ - return Address(0x7E570000 + index) - - -default_address = fixed_address(0) - - -def simple_transaction( - target: Address = default_address, payload: bytes = b"", gas_limit: int = 10_000_000 -): - """ - Creates a simple transaction - Parameters - ---------- - target the target address, defaults to 0x100 - payload the payload, defauls to empty - - Returns - ------- - a transaction instance that can be passed into state_tests - """ - return Transaction( - nonce=1, to=target, gas_limit=gas_limit, gas_price=10, protected=False, data=payload - ) diff --git a/tests/prague/eip7692_eof_v1/eip7620_eof_create/test_eofcreate.py b/tests/prague/eip7692_eof_v1/eip7620_eof_create/test_eofcreate.py index 22b6335767..2446cfe5f4 100644 --- a/tests/prague/eip7692_eof_v1/eip7620_eof_create/test_eofcreate.py +++ b/tests/prague/eip7692_eof_v1/eip7620_eof_create/test_eofcreate.py @@ -6,9 +6,10 @@ from ethereum_test_tools import ( Account, + Alloc, Environment, StateTestFiller, - TestAddress, + Transaction, compute_eofcreate_address, ) from ethereum_test_tools.eof.v1 import Container, Section @@ -16,9 +17,6 @@ from .. import EOF_FORK_NAME from .helpers import ( - default_address, - fixed_address, - simple_transaction, slot_call_result, slot_calldata, slot_code_worked, @@ -41,41 +39,47 @@ def test_simple_eofcreate( state_test: StateTestFiller, + pre: Alloc, ): """ Verifies a simple EOFCREATE case """ env = Environment() - pre = { - TestAddress: Account(balance=10**21, nonce=1), - default_address: Account( - code=Container( - sections=[ - Section.Code( - code=Op.SSTORE(0, Op.EOFCREATE[0](0, 0, 0, 0)) + Op.STOP, - max_stack_height=4, - ), - Section.Container(container=smallest_initcode_subcontainer), - ], - data=b"abcdef", - ), - storage={0: 0xB17D}, # a canary to be overwritten + sender = pre.fund_eoa() + contract_address = pre.deploy_contract( + code=Container( + sections=[ + Section.Code( + code=Op.SSTORE(0, Op.EOFCREATE[0](0, 0, 0, 0)) + Op.STOP, + max_stack_height=4, + ), + Section.Container(container=smallest_initcode_subcontainer), + ], + data=b"abcdef", ), - } + storage={0: 0xB17D}, # a canary to be overwritten + ) # Storage in 0 should have the address, post = { - default_address: Account( + contract_address: Account( storage={ - 0: compute_eofcreate_address(default_address, 0, smallest_initcode_subcontainer) + 0: compute_eofcreate_address(contract_address, 0, smallest_initcode_subcontainer) } ) } - - state_test(env=env, pre=pre, post=post, tx=simple_transaction()) + tx = Transaction( + to=contract_address, + gas_limit=10_000_000, + gas_price=10, + protected=False, + sender=sender, + ) + state_test(env=env, pre=pre, post=post, tx=tx) def test_eofcreate_then_call( state_test: StateTestFiller, + pre: Alloc, ): """ Verifies a simple EOFCREATE case, and then calls the deployed contract @@ -99,34 +103,41 @@ def test_eofcreate_then_call( ] ) - callable_address = compute_eofcreate_address(default_address, 0, callable_contract_initcode) - pre = { - TestAddress: Account(balance=10**21, nonce=1), - default_address: Account( - code=Container( - sections=[ - Section.Code( - code=Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, 0)) - + Op.EXTCALL(Op.SLOAD(slot_create_address), 0, 0, 0) - + Op.SSTORE(slot_code_worked, value_code_worked) - + Op.STOP, - max_stack_height=4, - ), - Section.Container(container=callable_contract_initcode), - ], - ) - ), - } + sender = pre.fund_eoa() + contract_address = pre.deploy_contract( + Container( + sections=[ + Section.Code( + code=Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, 0)) + + Op.EXTCALL(Op.SLOAD(slot_create_address), 0, 0, 0) + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP, + max_stack_height=4, + ), + Section.Container(container=callable_contract_initcode), + ], + ) + ) + + callable_address = compute_eofcreate_address(contract_address, 0, callable_contract_initcode) + # Storage in 0 should have the address, # post = { - default_address: Account( + contract_address: Account( storage={slot_create_address: callable_address, slot_code_worked: value_code_worked} ), callable_address: Account(storage={slot_code_worked: value_code_worked}), } - state_test(env=env, pre=pre, post=post, tx=simple_transaction()) + tx = Transaction( + to=contract_address, + gas_limit=10_000_000, + gas_price=10, + protected=False, + sender=sender, + ) + state_test(env=env, pre=pre, post=post, tx=tx) @pytest.mark.parametrize( @@ -140,7 +151,7 @@ def test_eofcreate_then_call( pytest.param(b"aabbccddeeffgghhii", id="extra"), ], ) -def test_auxdata_variations(state_test: StateTestFiller, auxdata_bytes: bytes): +def test_auxdata_variations(state_test: StateTestFiller, pre: Alloc, auxdata_bytes: bytes): """ Verifies that auxdata bytes are correctly handled in RETURNCONTRACT """ @@ -170,28 +181,26 @@ def test_auxdata_variations(state_test: StateTestFiller, auxdata_bytes: bytes): ], ) - pre = { - TestAddress: Account(balance=10**21, nonce=1), - default_address: Account( - code=Container( - sections=[ - Section.Code( - code=Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, 0)) + Op.STOP, - max_stack_height=4, - ), - Section.Container(container=initcode_subcontainer), - ] - ), - storage={slot_create_address: value_canary_to_be_overwritten}, + sender = pre.fund_eoa() + contract_address = pre.deploy_contract( + code=Container( + sections=[ + Section.Code( + code=Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, 0)) + Op.STOP, + max_stack_height=4, + ), + Section.Container(container=initcode_subcontainer), + ] ), - } + storage={slot_create_address: value_canary_to_be_overwritten}, + ) # Storage in 0 should have the address, post = { - default_address: Account( + contract_address: Account( storage={ slot_create_address: compute_eofcreate_address( - default_address, 0, initcode_subcontainer + contract_address, 0, initcode_subcontainer ) if deploy_success else b"\0" @@ -199,10 +208,18 @@ def test_auxdata_variations(state_test: StateTestFiller, auxdata_bytes: bytes): ) } - state_test(env=env, pre=pre, post=post, tx=simple_transaction()) + tx = Transaction( + to=contract_address, + gas_limit=10_000_000, + gas_price=10, + protected=False, + sender=sender, + ) + + state_test(env=env, pre=pre, post=post, tx=tx) -def test_calldata(state_test: StateTestFiller): +def test_calldata(state_test: StateTestFiller, pre: Alloc): """ Verifies CALLDATA passing through EOFCREATE """ @@ -223,22 +240,20 @@ def test_calldata(state_test: StateTestFiller): calldata_size = 32 calldata = b"\x45" * calldata_size - pre = { - TestAddress: Account(balance=10**21, nonce=1), - default_address: Account( - code=Container( - sections=[ - Section.Code( - code=Op.MSTORE(0, Op.PUSH32(calldata)) - + Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, calldata_size)) - + Op.STOP, - max_stack_height=4, - ), - Section.Container(container=initcode_subcontainer), - ] - ) - ), - } + sender = pre.fund_eoa() + contract_address = pre.deploy_contract( + code=Container( + sections=[ + Section.Code( + code=Op.MSTORE(0, Op.PUSH32(calldata)) + + Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, calldata_size)) + + Op.STOP, + max_stack_height=4, + ), + Section.Container(container=initcode_subcontainer), + ] + ) + ) # deployed contract is smallest plus data deployed_contract = Container( @@ -250,17 +265,26 @@ def test_calldata(state_test: StateTestFiller): ) # factory contract Storage in 0 should have the created address, # created contract storage in 0 should have the calldata - created_address = compute_eofcreate_address(default_address, 0, initcode_subcontainer) + created_address = compute_eofcreate_address(contract_address, 0, initcode_subcontainer) post = { - default_address: Account(storage={slot_create_address: created_address}), + contract_address: Account(storage={slot_create_address: created_address}), created_address: Account(code=deployed_contract, storage={slot_calldata: calldata}), } - state_test(env=env, pre=pre, post=post, tx=simple_transaction()) + tx = Transaction( + to=contract_address, + gas_limit=10_000_000, + gas_price=10, + protected=False, + sender=sender, + ) + + state_test(env=env, pre=pre, post=post, tx=tx) def test_eofcreate_in_initcode( state_test: StateTestFiller, + pre: Alloc, ): """ Verifies an EOFCREATE occuring within initcode creates that contract @@ -279,27 +303,25 @@ def test_eofcreate_in_initcode( ) env = Environment() - pre = { - TestAddress: Account(balance=10**21, nonce=1), - default_address: Account( - code=Container( - sections=[ - Section.Code( - code=Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, 0)) - + Op.SSTORE(slot_code_worked, value_code_worked) - + Op.STOP, - max_stack_height=4, - ), - Section.Container(container=nested_initcode_subcontainer), - ] - ) - ), - } + sender = pre.fund_eoa() + contract_address = pre.deploy_contract( + code=Container( + sections=[ + Section.Code( + code=Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, 0)) + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP, + max_stack_height=4, + ), + Section.Container(container=nested_initcode_subcontainer), + ] + ) + ) - outer_address = compute_eofcreate_address(default_address, 0, nested_initcode_subcontainer) + outer_address = compute_eofcreate_address(contract_address, 0, nested_initcode_subcontainer) inner_address = compute_eofcreate_address(outer_address, 0, smallest_initcode_subcontainer) post = { - default_address: Account( + contract_address: Account( storage={slot_create_address: outer_address, slot_code_worked: value_code_worked} ), outer_address: Account( @@ -307,11 +329,20 @@ def test_eofcreate_in_initcode( ), } - state_test(env=env, pre=pre, post=post, tx=simple_transaction()) + tx = Transaction( + to=contract_address, + gas_limit=10_000_000, + gas_price=10, + protected=False, + sender=sender, + ) + + state_test(env=env, pre=pre, post=post, tx=tx) def test_eofcreate_in_initcode_reverts( state_test: StateTestFiller, + pre: Alloc, ): """ Verifies an EOFCREATE occuring in an initcode is rolled back when the initcode reverts @@ -330,28 +361,26 @@ def test_eofcreate_in_initcode_reverts( ) env = Environment() - pre = { - TestAddress: Account(balance=10**21, nonce=1), - default_address: Account( - code=Container( - sections=[ - Section.Code( - code=Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, 0)) - + Op.SSTORE(slot_code_worked, value_code_worked) - + Op.STOP, - max_stack_height=4, - ), - Section.Container(container=nested_initcode_subcontainer), - ] - ), - storage={slot_create_address: value_canary_to_be_overwritten}, + sender = pre.fund_eoa() + contract_address = pre.deploy_contract( + code=Container( + sections=[ + Section.Code( + code=Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, 0)) + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP, + max_stack_height=4, + ), + Section.Container(container=nested_initcode_subcontainer), + ] ), - } + storage={slot_create_address: value_canary_to_be_overwritten}, + ) - outer_address = compute_eofcreate_address(default_address, 0, nested_initcode_subcontainer) + outer_address = compute_eofcreate_address(contract_address, 0, nested_initcode_subcontainer) inner_address = compute_eofcreate_address(outer_address, 0, smallest_initcode_subcontainer) post = { - default_address: Account( + contract_address: Account( storage={ slot_create_address: 0, slot_code_worked: value_code_worked, @@ -360,56 +389,62 @@ def test_eofcreate_in_initcode_reverts( outer_address: Account.NONEXISTENT, inner_address: Account.NONEXISTENT, } - - state_test(env=env, pre=pre, post=post, tx=simple_transaction()) + tx = Transaction( + to=contract_address, + gas_limit=10_000_000, + gas_price=10, + protected=False, + sender=sender, + ) + state_test(env=env, pre=pre, post=post, tx=tx) def test_return_data_cleared( state_test: StateTestFiller, + pre: Alloc, ): """ Verifies the return data is not re-used from a extcall but is cleared upon eofcreate """ env = Environment() - callable_address = fixed_address(1) value_return_canary = 0x4158675309 value_return_canary_size = 5 - callable_contract = Container( - sections=[ - Section.Code( - code=Op.MSTORE(0, value_return_canary) + Op.RETURN(0, value_return_canary_size), - max_stack_height=2, - ) - ] + callable_address = pre.deploy_contract( + code=Container( + sections=[ + Section.Code( + code=Op.MSTORE(0, value_return_canary) + + Op.RETURN(0, value_return_canary_size), + max_stack_height=2, + ) + ] + ) ) slot_returndata_size_2 = slot_last_slot * 2 + slot_returndata_size - pre = { - TestAddress: Account(balance=10**21, nonce=1), - default_address: Account( - code=Container( - sections=[ - Section.Code( - code=Op.SSTORE(slot_call_result, Op.EXTCALL(callable_address, 0, 0, 0)) - + Op.SSTORE(slot_returndata_size, Op.RETURNDATASIZE) - + Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, 0)) - + Op.SSTORE(slot_returndata_size_2, Op.RETURNDATASIZE) - + Op.SSTORE(slot_code_worked, value_code_worked) - + Op.STOP, - max_stack_height=4, - ), - Section.Container(container=smallest_initcode_subcontainer), - ], - ) - ), - callable_address: Account(code=callable_contract, nonce=1), - } + sender = pre.fund_eoa() + contract_address = pre.deploy_contract( + code=Container( + sections=[ + Section.Code( + code=Op.SSTORE(slot_call_result, Op.EXTCALL(callable_address, 0, 0, 0)) + + Op.SSTORE(slot_returndata_size, Op.RETURNDATASIZE) + + Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, 0)) + + Op.SSTORE(slot_returndata_size_2, Op.RETURNDATASIZE) + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP, + max_stack_height=4, + ), + Section.Container(container=smallest_initcode_subcontainer), + ], + ) + ) new_contract_address = compute_eofcreate_address( - default_address, 0, smallest_initcode_subcontainer + contract_address, 0, smallest_initcode_subcontainer ) post = { - default_address: Account( + contract_address: Account( storage={ slot_call_result: value_call_result_success, slot_returndata_size: value_return_canary_size, @@ -417,53 +452,62 @@ def test_return_data_cleared( slot_returndata_size_2: 0, slot_code_worked: value_code_worked, }, - nonce=1, + nonce=2, ), callable_address: Account(nonce=1), new_contract_address: Account(nonce=1), } + tx = Transaction( + to=contract_address, + gas_limit=10_000_000, + gas_price=10, + protected=False, + sender=sender, + ) - state_test(env=env, pre=pre, post=post, tx=simple_transaction()) + state_test(env=env, pre=pre, post=post, tx=tx) def test_address_collision( state_test: StateTestFiller, + pre: Alloc, ): """ Verifies a simple EOFCREATE case """ env = Environment() + slot_create_address_2 = slot_last_slot * 2 + slot_create_address + slot_create_address_3 = slot_last_slot * 3 + slot_create_address + sender = pre.fund_eoa() + contract_address = pre.deploy_contract( + code=Container( + sections=[ + Section.Code( + code=Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, 0)) + + Op.SSTORE(slot_create_address_2, Op.EOFCREATE[0](0, 0, 0, 0)) + + Op.SSTORE(slot_create_address_3, Op.EOFCREATE[0](0, 1, 0, 0)) + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP, + max_stack_height=4, + ), + Section.Container(container=smallest_initcode_subcontainer), + ], + ) + ) salt_zero_address = compute_eofcreate_address( - default_address, 0, smallest_initcode_subcontainer + contract_address, 0, smallest_initcode_subcontainer ) salt_one_address = compute_eofcreate_address( - default_address, 1, smallest_initcode_subcontainer + contract_address, 1, smallest_initcode_subcontainer ) - slot_create_address_2 = slot_last_slot * 2 + slot_create_address - slot_create_address_3 = slot_last_slot * 3 + slot_create_address - pre = { - TestAddress: Account(balance=10**21, nonce=1), - default_address: Account( - code=Container( - sections=[ - Section.Code( - code=Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, 0)) - + Op.SSTORE(slot_create_address_2, Op.EOFCREATE[0](0, 0, 0, 0)) - + Op.SSTORE(slot_create_address_3, Op.EOFCREATE[0](0, 1, 0, 0)) - + Op.SSTORE(slot_code_worked, value_code_worked) - + Op.STOP, - max_stack_height=4, - ), - Section.Container(container=smallest_initcode_subcontainer), - ], - ) - ), - salt_one_address: Account(balance=1, nonce=1), - } + # Hard-code address for collision, no other way to do this. + # We should mark tests that do this, and fail on unmarked tests. + pre[salt_one_address] = Account(balance=1, nonce=1) + post = { - default_address: Account( + contract_address: Account( storage={ slot_create_address: salt_zero_address, slot_create_address_2: value_create_failed, # had an in-transaction collision @@ -474,4 +518,11 @@ def test_address_collision( } # Multiple create fails is expensive, use an absurd amount of gas - state_test(env=env, pre=pre, post=post, tx=simple_transaction(gas_limit=300_000_000_000)) + tx = Transaction( + to=contract_address, + gas_limit=300_000_000_000, + gas_price=10, + protected=False, + sender=sender, + ) + state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/prague/eip7692_eof_v1/eip7620_eof_create/test_eofcreate_failures.py b/tests/prague/eip7692_eof_v1/eip7620_eof_create/test_eofcreate_failures.py index 826dd3fa7f..03e03213f9 100644 --- a/tests/prague/eip7692_eof_v1/eip7620_eof_create/test_eofcreate_failures.py +++ b/tests/prague/eip7692_eof_v1/eip7620_eof_create/test_eofcreate_failures.py @@ -6,9 +6,10 @@ from ethereum_test_tools import ( Account, + Alloc, Environment, StateTestFiller, - TestAddress, + Transaction, compute_eofcreate_address, ) from ethereum_test_tools.eof.v1 import Container, Section @@ -17,8 +18,6 @@ from .. import EOF_FORK_NAME from .helpers import ( - default_address, - simple_transaction, slot_code_should_fail, slot_code_worked, slot_create_address, @@ -44,7 +43,7 @@ pytest.param(b"\x08\xc3\x79\xa0", id="Error(string)"), ], ) -def test_initcode_revert(state_test: StateTestFiller, revert: bytes): +def test_initcode_revert(state_test: StateTestFiller, pre: Alloc, revert: bytes): """ Verifies proper handling of REVERT in initcode """ @@ -77,13 +76,11 @@ def test_initcode_revert(state_test: StateTestFiller, revert: bytes): ], ) - pre = { - TestAddress: Account(balance=10**21, nonce=1), - default_address: Account(code=factory_contract), - } + sender = pre.fund_eoa() + contract_address = pre.deploy_contract(code=factory_contract) post = { - default_address: Account( + contract_address: Account( storage={ slot_create_address: value_create_failed, slot_returndata_size: revert_size, @@ -92,53 +89,64 @@ def test_initcode_revert(state_test: StateTestFiller, revert: bytes): } ) } - - state_test(env=env, pre=pre, post=post, tx=simple_transaction()) + tx = Transaction( + to=contract_address, + gas_limit=10_000_000, + gas_price=10, + protected=False, + sender=sender, + ) + state_test(env=env, pre=pre, post=post, tx=tx) def test_initcode_aborts( state_test: StateTestFiller, + pre: Alloc, ): """ Verifies correct handling of a halt in EOF initcode """ env = Environment() - pre = { - TestAddress: Account(balance=10**21, nonce=1), - default_address: Account( - code=Container( - sections=[ - Section.Code( - code=Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, 0)) - + Op.SSTORE(slot_code_worked, value_code_worked) - + Op.STOP, - max_stack_height=4, - ), - Section.Container( - container=Container( - sections=[ - Section.Code( - code=Op.INVALID, - max_stack_height=0, - ) - ] - ) - ), - ] - ) - ), - } + sender = pre.fund_eoa() + contract_address = pre.deploy_contract( + code=Container( + sections=[ + Section.Code( + code=Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, 0)) + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP, + max_stack_height=4, + ), + Section.Container( + container=Container( + sections=[ + Section.Code( + code=Op.INVALID, + ) + ] + ) + ), + ] + ) + ) # Storage in slot_create_address should not have the address, post = { - default_address: Account( + contract_address: Account( storage={ slot_create_address: value_create_failed, slot_code_worked: value_code_worked, } ) } + tx = Transaction( + to=contract_address, + gas_limit=10_000_000, + gas_price=10, + protected=False, + sender=sender, + ) - state_test(env=env, pre=pre, post=post, tx=simple_transaction()) + state_test(env=env, pre=pre, post=post, tx=tx) """ @@ -169,6 +177,7 @@ def test_initcode_aborts( ) def test_eofcreate_deploy_sizes( state_test: StateTestFiller, + pre: Alloc, target_deploy_size: int, ): """ @@ -204,30 +213,28 @@ def test_eofcreate_deploy_sizes( len(initcode_subcontainer), ) - pre = { - TestAddress: Account(balance=10**21, nonce=1), - default_address: Account( - code=Container( - sections=[ - Section.Code( - code=Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, 0)) - + Op.SSTORE(slot_code_worked, value_code_worked) - + Op.STOP, - max_stack_height=4, - ), - Section.Container(container=initcode_subcontainer), - ] - ) - ), - } + sender = pre.fund_eoa() + contract_address = pre.deploy_contract( + code=Container( + sections=[ + Section.Code( + code=Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, 0)) + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP, + max_stack_height=4, + ), + Section.Container(container=initcode_subcontainer), + ] + ) + ) # Storage in 0 should have the address, # Storage 1 is a canary of 1 to make sure it tried to execute, which also covers cases of # data+code being greater than initcode_size_max, which is allowed. post = { - default_address: Account( + contract_address: Account( storage={ slot_create_address: compute_eofcreate_address( - default_address, 0, initcode_subcontainer + contract_address, 0, initcode_subcontainer ) if target_deploy_size <= MAX_BYTECODE_SIZE else value_create_failed, @@ -235,8 +242,15 @@ def test_eofcreate_deploy_sizes( } ) } + tx = Transaction( + to=contract_address, + gas_limit=10_000_000, + gas_price=10, + protected=False, + sender=sender, + ) - state_test(env=env, pre=pre, post=post, tx=simple_transaction()) + state_test(env=env, pre=pre, post=post, tx=tx) @pytest.mark.parametrize( @@ -273,7 +287,7 @@ def test_eofcreate_deploy_sizes_tx( pytest.param(0x10000 + 1, id="over64k"), ], ) -def test_auxdata_size_failures(state_test: StateTestFiller, auxdata_size: int): +def test_auxdata_size_failures(state_test: StateTestFiller, pre: Alloc, auxdata_size: int): """ Exercises a number of auxdata size violations, and one maxcode success """ @@ -292,33 +306,31 @@ def test_auxdata_size_failures(state_test: StateTestFiller, auxdata_size: int): ], ) - pre = { - TestAddress: Account(balance=10**21, nonce=1), - default_address: Account( - code=Container( - sections=[ - Section.Code( - code=Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) - + Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, Op.CALLDATASIZE)) - + Op.SSTORE(slot_code_worked, value_code_worked) - + Op.STOP, - max_stack_height=4, - ), - Section.Container(container=initcode_subcontainer), - ] - ) - ), - } + sender = pre.fund_eoa() + contract_address = pre.deploy_contract( + code=Container( + sections=[ + Section.Code( + code=Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + + Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, Op.CALLDATASIZE)) + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP, + max_stack_height=4, + ), + Section.Container(container=initcode_subcontainer), + ] + ) + ) deployed_container_size = len(smallest_runtime_subcontainer) + auxdata_size # Storage in 0 will have address in first test, 0 in all other cases indicating failure # Storage 1 in 1 is a canary to see if EOFCREATE opcode halted post = { - default_address: Account( + contract_address: Account( storage={ slot_create_address: compute_eofcreate_address( - default_address, 0, initcode_subcontainer + contract_address, 0, initcode_subcontainer ) if deployed_container_size <= MAX_BYTECODE_SIZE else 0, @@ -327,7 +339,16 @@ def test_auxdata_size_failures(state_test: StateTestFiller, auxdata_size: int): ) } - state_test(env=env, pre=pre, post=post, tx=simple_transaction(payload=auxdata_bytes)) + tx = Transaction( + to=contract_address, + gas_limit=10_000_000, + gas_price=10, + protected=False, + sender=sender, + data=auxdata_bytes, + ) + + state_test(env=env, pre=pre, post=post, tx=tx) @pytest.mark.parametrize( @@ -339,6 +360,7 @@ def test_auxdata_size_failures(state_test: StateTestFiller, auxdata_size: int): ) def test_eofcreate_insufficient_stipend( state_test: StateTestFiller, + pre: Alloc, value: int, ): """ @@ -357,27 +379,35 @@ def test_eofcreate_insufficient_stipend( Section.Container(container=smallest_initcode_subcontainer), ] ) - pre = { - TestAddress: Account(balance=10**11, nonce=1), - default_address: Account(balance=value - 1, code=initcode_container), - } + sender = pre.fund_eoa(10**11) + contract_address = pre.deploy_contract( + code=initcode_container, + balance=value - 1, + ) # create will fail but not trigger a halt, so canary at storage 1 should be set # also validate target created contract fails post = { - default_address: Account( + contract_address: Account( storage={ slot_create_address: value_create_failed, slot_code_worked: value_code_worked, } ), - compute_eofcreate_address(default_address, 0, initcode_container): Account.NONEXISTENT, + compute_eofcreate_address(contract_address, 0, initcode_container): Account.NONEXISTENT, } - - state_test(env=env, pre=pre, post=post, tx=simple_transaction()) + tx = Transaction( + to=contract_address, + gas_limit=10_000_000, + gas_price=10, + protected=False, + sender=sender, + ) + state_test(env=env, pre=pre, post=post, tx=tx) def test_insufficient_initcode_gas( state_test: StateTestFiller, + pre: Alloc, ): """ Excercises an EOFCREATE when there is not enough gas for the initcode charge @@ -397,45 +427,51 @@ def test_insufficient_initcode_gas( ], ) - pre = { - TestAddress: Account(balance=10**21, nonce=1), - default_address: Account( - code=Container( - sections=[ - Section.Code( - code=Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, 0)) - + Op.SSTORE(slot_code_should_fail, value_code_worked) - + Op.STOP, - max_stack_height=4, - ), - Section.Container(container=initcode_container), - ], - ), - storage={ - slot_create_address: value_canary_should_not_change, - slot_code_should_fail: value_canary_should_not_change, - }, + sender = pre.fund_eoa() + contract_address = pre.deploy_contract( + code=Container( + sections=[ + Section.Code( + code=Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, 0)) + + Op.SSTORE(slot_code_should_fail, value_code_worked) + + Op.STOP, + max_stack_height=4, + ), + Section.Container(container=initcode_container), + ], ), - } + storage={ + slot_create_address: value_canary_should_not_change, + slot_code_should_fail: value_canary_should_not_change, + }, + ) # enough gas for everything but EVM opcodes and EIP-150 reserves gas_limit = 21_000 + 32_000 + (len(initcode_data) + 31) // 32 * 6 # out_of_gas is triggered, so canary won't set value # also validate target created contract fails post = { - default_address: Account( + contract_address: Account( storage={ slot_create_address: value_canary_should_not_change, slot_code_should_fail: value_canary_should_not_change, }, ), - compute_eofcreate_address(default_address, 0, initcode_container): Account.NONEXISTENT, + compute_eofcreate_address(contract_address, 0, initcode_container): Account.NONEXISTENT, } + tx = Transaction( + to=contract_address, + gas_limit=gas_limit, + gas_price=10, + protected=False, + sender=sender, + ) - state_test(env=env, pre=pre, post=post, tx=simple_transaction(gas_limit=gas_limit)) + state_test(env=env, pre=pre, post=post, tx=tx) def test_insufficient_gas_memory_expansion( state_test: StateTestFiller, + pre: Alloc, ): """ Excercises an EOFCREATE when the memory for auxdata has not been expanded but is requested @@ -454,16 +490,14 @@ def test_insufficient_gas_memory_expansion( Section.Container(container=smallest_initcode_subcontainer), ], ) - pre = { - TestAddress: Account(balance=10**21, nonce=1), - default_address: Account( - code=initcode_container, - storage={ - slot_create_address: value_canary_should_not_change, - slot_code_should_fail: value_canary_should_not_change, - }, - ), - } + sender = pre.fund_eoa() + contract_address = pre.deploy_contract( + code=initcode_container, + storage={ + slot_create_address: value_canary_should_not_change, + slot_code_should_fail: value_canary_should_not_change, + }, + ) # enough gas for everything but EVM opcodes and EIP-150 reserves initcode_container_words = (len(initcode_container) + 31) // 32 auxdata_size_words = (auxdata_size + 31) // 32 @@ -477,20 +511,28 @@ def test_insufficient_gas_memory_expansion( # out_of_gas is triggered, so canary won't set value # also validate target created contract fails post = { - default_address: Account( + contract_address: Account( storage={ slot_create_address: value_canary_should_not_change, slot_code_should_fail: value_canary_should_not_change, }, ), - compute_eofcreate_address(default_address, 0, initcode_container): Account.NONEXISTENT, + compute_eofcreate_address(contract_address, 0, initcode_container): Account.NONEXISTENT, } + tx = Transaction( + to=contract_address, + gas_limit=gas_limit, + gas_price=10, + protected=False, + sender=sender, + ) - state_test(env=env, pre=pre, post=post, tx=simple_transaction(gas_limit=gas_limit)) + state_test(env=env, pre=pre, post=post, tx=tx) def test_insufficient_returncontract_auxdata_gas( state_test: StateTestFiller, + pre: Alloc, ): """ Excercises an EOFCREATE when there is not enough gas for the initcode charge @@ -509,26 +551,24 @@ def test_insufficient_returncontract_auxdata_gas( ], ) - pre = { - TestAddress: Account(balance=10**21, nonce=1), - default_address: Account( - code=Container( - sections=[ - Section.Code( - code=Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, 0)) - + Op.SSTORE(slot_code_should_fail, value_code_worked) - + Op.STOP, - max_stack_height=4, - ), - Section.Container(container=initcode_container), - ], - ), - storage={ - slot_create_address: value_canary_should_not_change, - slot_code_should_fail: value_canary_should_not_change, - }, + sender = pre.fund_eoa() + contract_address = pre.deploy_contract( + code=Container( + sections=[ + Section.Code( + code=Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, 0)) + + Op.SSTORE(slot_code_should_fail, value_code_worked) + + Op.STOP, + max_stack_height=4, + ), + Section.Container(container=initcode_container), + ], ), - } + storage={ + slot_create_address: value_canary_should_not_change, + slot_code_should_fail: value_canary_should_not_change, + }, + ) # enough gas for everything but EVM opcodes and EIP-150 reserves initcode_container_words = (len(initcode_container) + 31) // 32 auxdata_size_words = (auxdata_size + 31) // 32 @@ -542,13 +582,20 @@ def test_insufficient_returncontract_auxdata_gas( # out_of_gas is triggered, so canary won't set value # also validate target created contract fails post = { - default_address: Account( + contract_address: Account( storage={ slot_create_address: value_canary_should_not_change, slot_code_should_fail: value_canary_should_not_change, }, ), - compute_eofcreate_address(default_address, 0, initcode_container): Account.NONEXISTENT, + compute_eofcreate_address(contract_address, 0, initcode_container): Account.NONEXISTENT, } + tx = Transaction( + to=contract_address, + gas_limit=gas_limit, + gas_price=10, + protected=False, + sender=sender, + ) - state_test(env=env, pre=pre, post=post, tx=simple_transaction(gas_limit=gas_limit)) + state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/prague/eip7692_eof_v1/eip7620_eof_create/test_legacy_eof_creates.py b/tests/prague/eip7692_eof_v1/eip7620_eof_create/test_legacy_eof_creates.py index 1d756a832c..2e0708553d 100644 --- a/tests/prague/eip7692_eof_v1/eip7620_eof_create/test_legacy_eof_creates.py +++ b/tests/prague/eip7692_eof_v1/eip7620_eof_create/test_legacy_eof_creates.py @@ -6,16 +6,14 @@ import pytest -from ethereum_test_tools import Account, Environment +from ethereum_test_tools import Account, Alloc, Environment from ethereum_test_tools import Initcode as LegacyInitcode -from ethereum_test_tools import StateTestFiller, TestAddress +from ethereum_test_tools import StateTestFiller, Transaction from ethereum_test_tools.vm.opcode import Opcodes from ethereum_test_tools.vm.opcode import Opcodes as Op from .. import EOF_FORK_NAME from .helpers import ( - default_address, - simple_transaction, slot_code_worked, slot_create_address, smallest_initcode_subcontainer, @@ -46,6 +44,7 @@ ) def test_cross_version_creates_fail( state_test: StateTestFiller, + pre: Alloc, legacy_create_opcode: Opcodes, deploy_code: SupportsBytes, ): @@ -54,33 +53,38 @@ def test_cross_version_creates_fail( """ env = Environment() salt_param = [0] if legacy_create_opcode == Op.CREATE2 else [] - pre = { - TestAddress: Account(balance=10**21, nonce=1), - default_address: Account( - code=Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) - + Op.SSTORE( - slot_create_address, legacy_create_opcode(0, 0, Op.CALLDATASIZE, *salt_param) - ) - + Op.SSTORE(slot_code_worked, value_code_worked) - + Op.STOP - ), - } + sender = pre.fund_eoa() + contract_address = pre.deploy_contract( + code=Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + + Op.SSTORE(slot_create_address, legacy_create_opcode(0, 0, Op.CALLDATASIZE, *salt_param)) + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP + ) + # Storage in 0 should be empty as the create/create2 should fail, # and 1 in 1 to show execution continued and did not halt post = { - default_address: Account( + contract_address: Account( storage={ slot_create_address: value_create_failed, slot_code_worked: value_code_worked, } ) } + tx = Transaction( + to=contract_address, + gas_limit=10_000_000, + gas_price=10, + protected=False, + sender=sender, + data=deploy_code, + ) state_test( env=env, pre=pre, post=post, - tx=simple_transaction(payload=bytes(deploy_code)), + tx=tx, ) @@ -100,6 +104,7 @@ def test_cross_version_creates_fail( ) def test_legacy_initcode_eof_contract_fails( state_test: StateTestFiller, + pre: Alloc, legacy_create_opcode: Opcodes, deploy_code: SupportsBytes, ): @@ -115,16 +120,23 @@ def test_legacy_initcode_eof_contract_fails( + Op.SSTORE(slot_code_worked, value_code_worked) ) - pre = { - TestAddress: Account(balance=10**21, nonce=1), - default_address: Account(code=factory_code), - } + sender = pre.fund_eoa() + contract_address = pre.deploy_contract(code=factory_code) + # Storage in 0 should be empty as the final CREATE filed # and 1 in 1 to show execution continued and did not halt post = { - default_address: Account( + contract_address: Account( storage={slot_create_address: value_create_failed, slot_code_worked: value_code_worked} ) } + tx = Transaction( + to=contract_address, + gas_limit=10_000_000, + gas_price=10, + protected=False, + data=init_code, + sender=sender, + ) - state_test(env=env, pre=pre, post=post, tx=simple_transaction(payload=bytes(init_code))) + state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/whitelist.txt b/whitelist.txt index 17c804ed50..58ed7c76ae 100644 --- a/whitelist.txt +++ b/whitelist.txt @@ -70,6 +70,7 @@ controlflow cp CPUs crypto +currentframe customizations Customizations danceratopz @@ -152,8 +153,10 @@ g2mul g2msm gaslimit gasprice +gcc GeneralStateTestsFiller gentest +getframeinfo geth geth's getitem @@ -273,6 +276,7 @@ pubkey px py pydantic +pyproject pyspelling pytest Pytest @@ -328,6 +332,7 @@ subclasscheck subdirectories subdirectory subgraph +subprocess subscriptable substring sudo @@ -342,6 +347,7 @@ tf ThreeHrSleep time15k timestamp +tmp todo toml toplevel @@ -362,6 +368,7 @@ ubuntu ukiyo uncomment undersize +usr util utils v0