From f651ab8b74e4c02504f8cf6ec23f49c45d946f39 Mon Sep 17 00:00:00 2001 From: spencer-tb Date: Mon, 24 Jul 2023 17:11:28 +0100 Subject: [PATCH 01/10] tests: Add initial set of beacon root tests. --- src/ethereum_test_forks/base_fork.py | 17 ++ src/ethereum_test_forks/forks/forks.py | 21 ++ src/ethereum_test_tools/__init__.py | 2 + src/ethereum_test_tools/common/__init__.py | 2 + src/ethereum_test_tools/common/constants.py | 1 + src/ethereum_test_tools/common/types.py | 36 +++ tests/cancun/eip4788_beacon_root/__init__.py | 3 + tests/cancun/eip4788_beacon_root/common.py | 63 +++++ tests/cancun/eip4788_beacon_root/conftest.py | 179 +++++++++++++ .../test_beacon_root_precompile.py | 250 ++++++++++++++++++ 10 files changed, 574 insertions(+) create mode 100644 tests/cancun/eip4788_beacon_root/__init__.py create mode 100644 tests/cancun/eip4788_beacon_root/common.py create mode 100644 tests/cancun/eip4788_beacon_root/conftest.py create mode 100644 tests/cancun/eip4788_beacon_root/test_beacon_root_precompile.py diff --git a/src/ethereum_test_forks/base_fork.py b/src/ethereum_test_forks/base_fork.py index aa04ad1602..f19e74f649 100644 --- a/src/ethereum_test_forks/base_fork.py +++ b/src/ethereum_test_forks/base_fork.py @@ -87,6 +87,14 @@ def header_blob_gas_used_required(cls, block_number: int = 0, timestamp: int = 0 """ pass + @classmethod + @abstractmethod + def header_beacon_root_required(cls, block_number: int, timestamp: int) -> bool: + """ + Returns true if the header must contain parent beacon block root + """ + pass + @classmethod @abstractmethod def get_reward(cls, block_number: int = 0, timestamp: int = 0) -> int: @@ -115,6 +123,15 @@ def engine_new_payload_blob_hashes(cls, block_number: int = 0, timestamp: int = """ pass + @classmethod + @abstractmethod + def engine_new_payload_beacon_root(cls, block_number: int = 0, timestamp: int = 0) -> bool: + """ + Returns true if the engine api version requires new payload calls to include a parent + beacon block root. + """ + pass + # Meta information about the fork @classmethod def name(cls) -> str: diff --git a/src/ethereum_test_forks/forks/forks.py b/src/ethereum_test_forks/forks/forks.py index 9efab49b9d..2e93501053 100644 --- a/src/ethereum_test_forks/forks/forks.py +++ b/src/ethereum_test_forks/forks/forks.py @@ -70,6 +70,13 @@ def engine_new_payload_version( """ return None + @classmethod + def header_beacon_root_required(cls, block_number: int, timestamp: int) -> bool: + """ + At genesis, header must not contain parent beacon block root + """ + return False + @classmethod def engine_new_payload_blob_hashes(cls, block_number: int = 0, timestamp: int = 0) -> bool: """ @@ -270,6 +277,13 @@ def header_blob_gas_used_required(cls, block_number: int = 0, timestamp: int = 0 """ return True + @classmethod + def header_beacon_root_required(cls, block_number: int, timestamp: int) -> bool: + """ + Parent beacon block root is required starting from Cancun. + """ + return True + @classmethod def engine_new_payload_version( cls, block_number: int = 0, timestamp: int = 0 @@ -285,3 +299,10 @@ def engine_new_payload_blob_hashes(cls, block_number: int = 0, timestamp: int = Starting at Cancun, payloads must have blob hashes. """ return True + + @classmethod + def engine_new_payload_beacon_root(cls, block_number: int = 0, timestamp: int = 0) -> bool: + """ + Starting at Cancun, payloads must have a parent beacon block root. + """ + return True diff --git a/src/ethereum_test_tools/__init__.py b/src/ethereum_test_tools/__init__.py index a99b518967..689fb2190c 100644 --- a/src/ethereum_test_tools/__init__.py +++ b/src/ethereum_test_tools/__init__.py @@ -8,6 +8,7 @@ AccessList, Account, Auto, + BeaconRoot, Block, EngineAPIError, Environment, @@ -52,6 +53,7 @@ "Auto", "BaseTest", "BaseTestConfig", + "BeaconRoot", "Block", "BlockchainTest", "BlockchainTestFiller", diff --git a/src/ethereum_test_tools/common/__init__.py b/src/ethereum_test_tools/common/__init__.py index c136dcf7f6..f9438f1b0b 100644 --- a/src/ethereum_test_tools/common/__init__.py +++ b/src/ethereum_test_tools/common/__init__.py @@ -4,6 +4,7 @@ from .constants import ( AddrAA, AddrBB, + BeaconRoot, EmptyTrieRoot, EngineAPIError, HistoryStorageAddress, @@ -61,6 +62,7 @@ "AddrBB", "Alloc", "Auto", + "BeaconRoot", "Block", "Bloom", "Bytes", diff --git a/src/ethereum_test_tools/common/constants.py b/src/ethereum_test_tools/common/constants.py index 06d06da0f3..aa5f1d3518 100644 --- a/src/ethereum_test_tools/common/constants.py +++ b/src/ethereum_test_tools/common/constants.py @@ -21,6 +21,7 @@ ZeroAddress = bytes([0] * 20) HistoryStorageAddress = "0x000000000000000000000000000000000000000b" +BeaconRoot = bytes.fromhex("3e97e493f9123f7455a3be1b388db32876beea7d165a3b63528d8f9a38b7249f") class EngineAPIError(IntEnum): diff --git a/src/ethereum_test_tools/common/types.py b/src/ethereum_test_tools/common/types.py index 9a454cf7a3..78c1c76d31 100644 --- a/src/ethereum_test_tools/common/types.py +++ b/src/ethereum_test_tools/common/types.py @@ -948,6 +948,13 @@ class Environment: cast_type=Number, ), ) + beacon_root: Optional[FixedSizeBytesConvertible] = field( + default=None, + json_encoder=JSONEncoder.Field( + name="beaconRoot", + cast_type=Hash, + ), + ) extra_data: Optional[BytesConvertible] = field( default=None, json_encoder=JSONEncoder.Field( @@ -1036,6 +1043,9 @@ def set_fork_requirements(self, fork: Fork) -> "Environment": ): res.blob_gas_used = 0 + if fork.header_beacon_root_required(number, timestamp) and res.beacon_root is None: + res.beacon_root = 0 + return res @@ -1798,6 +1808,7 @@ class Header: withdrawals_root: Optional[FixedSizeBytesConvertible | Removable] = None blob_gas_used: Optional[NumberConvertible | Removable] = None excess_blob_gas: Optional[NumberConvertible | Removable] = None + beacon_root: Optional[FixedSizeBytesConvertible | Removable] = None hash: Optional[FixedSizeBytesConvertible] = None REMOVE_FIELD: ClassVar[Removable] = Removable() @@ -2075,6 +2086,16 @@ class FixtureHeader: json_encoder=JSONEncoder.Field(name="excessBlobGas", cast_type=ZeroPaddedHexNumber), ) + beacon_root: Optional[Hash] = header_field( + default=None, + source=HeaderFieldSource( + parse_type=Hash, + fork_requirement_check="header_beacon_root_required", + source_environment="beacon_root", + ), + json_encoder=JSONEncoder.Field(name="beaconRoot"), + ) + hash: Optional[Hash] = header_field( default=None, source=HeaderFieldSource( @@ -2172,6 +2193,8 @@ def build( header.append(Uint(int(self.blob_gas_used))) if self.excess_blob_gas is not None: header.append(Uint(self.excess_blob_gas)) + if self.beacon_root is not None: + header.append(self.beacon_root) block = [ header, @@ -2253,6 +2276,8 @@ def set_environment(self, env: Environment) -> Environment: new_env.excess_blob_gas = self.excess_blob_gas if not isinstance(self.blob_gas_used, Removable): new_env.blob_gas_used = self.blob_gas_used + if not isinstance(self.beacon_root, Removable): + new_env.beacon_root = self.beacon_root """ These values are required, but they depend on the previous environment, so they can be calculated here. @@ -2422,6 +2447,14 @@ class FixtureEngineNewPayload: version: int = field( json_encoder=JSONEncoder.Field(), ) + beacon_root: Optional[FixedSizeBytesConvertible] = field( + default=None, + json_encoder=JSONEncoder.Field( + name="parentBeaconBlockRoot", + cast_type=Hash, + to_json=True, + ), + ) error_code: Optional[EngineAPIError] = field( default=None, json_encoder=JSONEncoder.Field( @@ -2460,6 +2493,9 @@ def from_fixture_header( transactions ) + if fork.engine_new_payload_beacon_root(header.number, header.timestamp): + new_payload.beacon_root = header.beacon_root + return new_payload diff --git a/tests/cancun/eip4788_beacon_root/__init__.py b/tests/cancun/eip4788_beacon_root/__init__.py new file mode 100644 index 0000000000..33d745b32c --- /dev/null +++ b/tests/cancun/eip4788_beacon_root/__init__.py @@ -0,0 +1,3 @@ +""" +Cross-client EIP-4788 Tests +""" diff --git a/tests/cancun/eip4788_beacon_root/common.py b/tests/cancun/eip4788_beacon_root/common.py new file mode 100644 index 0000000000..8e70eded09 --- /dev/null +++ b/tests/cancun/eip4788_beacon_root/common.py @@ -0,0 +1,63 @@ +""" +Common constants, classes & functions local to EIP-4788 tests. +""" + +from ethereum_test_tools import BeaconRoot, Storage + +REF_SPEC_4788_GIT_PATH = "EIPS/eip-4788.md" +REF_SPEC_4788_VERSION = "f0eb6a364aaf5ccb43516fa2c269a54fb881ecfd" + +BEACON_ROOT_PRECOMPILE_ADDRESS = 0x0B # HISTORY_STORE_ADDRESS +BEACON_ROOT_PRECOMPILE_GAS = 4_200 # G_BEACON_ROOT +HISTORICAL_ROOTS_MODULUS = 98_304 + +FORK_TIMESTAMP = 15_000 # ShanghaiToCancun timestamp +DEFAULT_BEACON_ROOT_HASH = BeaconRoot + + +def timestamp_index(timestamp: int) -> int: + """ + Derive the timestamp index into the timestamp ring buffer. + """ + return timestamp % HISTORICAL_ROOTS_MODULUS + + +def root_index(timestamp: int) -> int: + """ + Derive the root index into the root ring buffer. + """ + return timestamp_index(timestamp) + HISTORICAL_ROOTS_MODULUS + + +def expected_storage( + beacon_root: bytes, + timestamp: int, + valid_call: bool, + valid_input: bool, +) -> Storage: + """ + Derives the expected storage for a given beacon root precompile call + dependent on: + - success or failure of the call + - validity of the timestamp input used within the call + """ + storage: Storage.StorageDictType = dict() + # beacon root precompile call is successful + if valid_call: + storage[0] = 1 + storage[2] = 32 + # timestamp precompile input is valid + if valid_input: + storage[1] = beacon_root + else: + storage[1] = 0 + storage[3] = storage[1] + + # beacon root precompile call failed + else: + storage[0] = 0 + storage[1] = timestamp # due to failure, input is not overwritten + storage[2] = 0 + storage[3] = storage[1] + + return storage diff --git a/tests/cancun/eip4788_beacon_root/conftest.py b/tests/cancun/eip4788_beacon_root/conftest.py new file mode 100644 index 0000000000..4003e19dae --- /dev/null +++ b/tests/cancun/eip4788_beacon_root/conftest.py @@ -0,0 +1,179 @@ +""" +Shared pytest definitions local to EIP-4788 tests. +""" +from typing import Dict + +import pytest + +from ethereum_test_tools import ( + Account, + Environment, + TestAddress, + Transaction, + to_address, + to_hash_bytes, +) +from ethereum_test_tools.vm.opcode import Opcodes as Op + +from .common import ( + BEACON_ROOT_PRECOMPILE_ADDRESS, + BEACON_ROOT_PRECOMPILE_GAS, + DEFAULT_BEACON_ROOT_HASH, + expected_storage, +) + + +@pytest.fixture +def timestamp() -> int: # noqa: D103 + return 12 + + +@pytest.fixture +def beacon_root(request) -> bytes: # noqa: D103 + return to_hash_bytes(request.param) if hasattr(request, "param") else DEFAULT_BEACON_ROOT_HASH + + +@pytest.fixture +def env(timestamp: int, beacon_root: bytes) -> Environment: # noqa: D103 + return Environment( + timestamp=timestamp, + beacon_root=beacon_root, + ) + + +@pytest.fixture +def call_type() -> Op: # noqa: D103 + return Op.CALL + + +@pytest.fixture +def call_gas() -> int: # noqa: D103 + return BEACON_ROOT_PRECOMPILE_GAS + + +@pytest.fixture +def call_address() -> str: # noqa: D103 + return to_address(0x100) + + +@pytest.fixture +def precompile_call_account(call_type: Op, call_gas: int) -> Account: + """ + Code to call the beacon root precompile. + """ + precompile_call_code = Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + if call_type == Op.CALL or call_type == Op.CALLCODE: + precompile_call_code += Op.SSTORE( + 0, # store the result of the precompile call in storage[0] + call_type( + call_gas, + BEACON_ROOT_PRECOMPILE_ADDRESS, + 0x00, + 0x00, + Op.CALLDATASIZE, + 0x00, + 0x20, + ), + ) + elif call_type == Op.DELEGATECALL or call_type == Op.STATICCALL: + # delegatecall and staticcall use one less argument + precompile_call_code += Op.SSTORE( + 0, + call_type( + call_gas, + BEACON_ROOT_PRECOMPILE_ADDRESS, + 0x00, + Op.CALLDATASIZE, + 0x00, + 0x20, + ), + ) + precompile_call_code += ( + Op.SSTORE(1, Op.MLOAD(0x00)) + + Op.SSTORE(2, Op.RETURNDATASIZE) + + Op.RETURNDATACOPY(0, 0x0, Op.RETURNDATASIZE) + + Op.SSTORE(3, Op.MLOAD(0x00)) + ) + return Account( + nonce=0, + code=precompile_call_code, + balance=10**10, + ) + + +@pytest.fixture +def valid_call() -> bool: + """ + Validity of beacon root precompile call: defaults to True. + """ + return True + + +@pytest.fixture +def valid_input() -> bool: + """ + Validity of timestamp input to precompile call: defaults to True. + """ + return True + + +@pytest.fixture +def pre( + precompile_call_account: Account, + call_address: str, +) -> Dict: + """ + Prepares the pre state of all test cases, by setting the balance of the + source account of all test transactions, and the precompile caller account. + """ + return { + TestAddress: Account( + nonce=0, + balance=0x10**10, + ), + call_address: precompile_call_account, + } + + +@pytest.fixture +def tx( + call_address: str, + timestamp: int, +) -> Transaction: + """ + Prepares transaction to call the beacon root precompile caller account. + """ + return Transaction( + ty=2, + nonce=0, + data=to_hash_bytes(timestamp), + to=call_address, + value=0, + gas_limit=1000000, + max_fee_per_gas=7, + max_priority_fee_per_gas=0, + ) + + +@pytest.fixture +def post( + call_address: str, + beacon_root: bytes, + timestamp: int, + valid_call: bool, + valid_input: bool, +) -> Dict: + """ + Prepares the expected post state for a single precompile call based upon the success or + failure of the call, and the validity of the timestamp input. + """ + return { + call_address: Account( + storage=expected_storage( + beacon_root, + timestamp, + valid_call, + valid_input, + ), + ), + } diff --git a/tests/cancun/eip4788_beacon_root/test_beacon_root_precompile.py b/tests/cancun/eip4788_beacon_root/test_beacon_root_precompile.py new file mode 100644 index 0000000000..b845f39ab2 --- /dev/null +++ b/tests/cancun/eip4788_beacon_root/test_beacon_root_precompile.py @@ -0,0 +1,250 @@ +""" +abstract: Tests beacon block root for [EIP-4788: Beacon block root in the EVM](https://eips.ethereum.org/EIPS/eip-4788) + + Test the exposed beacon chain root in the EVM for [EIP-4788: Beacon block root in the EVM](https://eips.ethereum.org/EIPS/eip-4788) + +note: Adding a new test + + Add a function that is named `test_` and takes at least the following arguments: + + - blockchain_test or state_test + - env + - pre + - blocks or tx + - post + - valid_call + + The following arguments *need* to be parametrized or the test will not be generated: + + - + + All other `pytest.fixtures` can be parametrized to generate new combinations and test + cases. + +""" # noqa: E501 + +from typing import Dict + +import pytest + +from ethereum_test_tools import ( + Account, + Block, + BlockchainTestFiller, + Environment, + StateTestFiller, + Transaction, + to_address, + to_hash_bytes, +) +from ethereum_test_tools.vm.opcode import Opcodes as Op + +from .common import ( + BEACON_ROOT_PRECOMPILE_GAS, + DEFAULT_BEACON_ROOT_HASH, + HISTORICAL_ROOTS_MODULUS, + REF_SPEC_4788_GIT_PATH, + REF_SPEC_4788_VERSION, + expected_storage, + timestamp_index, +) + +REFERENCE_SPEC_GIT_PATH = REF_SPEC_4788_GIT_PATH +REFERENCE_SPEC_VERSION = REF_SPEC_4788_VERSION + + +@pytest.mark.parametrize( + "call_gas, valid_call", + [ + (BEACON_ROOT_PRECOMPILE_GAS, True), + (BEACON_ROOT_PRECOMPILE_GAS + 1, True), + (BEACON_ROOT_PRECOMPILE_GAS - 1, False), + ], +) +@pytest.mark.parametrize( + "call_type", + [ + Op.CALL, + Op.DELEGATECALL, + Op.CALLCODE, + Op.STATICCALL, + ], +) +@pytest.mark.valid_from("Cancun") +def test_beacon_root_precompile_calls( + state_test: StateTestFiller, + env: Environment, + pre: Dict, + tx: Transaction, + post: Dict, +): + """ + Tests the beacon root precompile call using various call contexts: + - `CALL` + - `DELEGATECALL` + - `CALLCODE` + - `STATICCALL` + for diffent call gas amounts: + - exact gas (valid call) + - extra gas (valid call) + - insufficient gas (invalid call) + + The expected result is that the precompile call will be executed if the gas amount is met + and return the correct`parent_beacon_block_root`. Otherwise the call will be invalid, and not + be executed. This is highlighted within storage by storing the return value of each call + context. + """ + state_test( + env=env, + pre=pre, + txs=[tx], + post=post, + ) + + +@pytest.mark.parametrize( + "timestamp, valid_input", + [ + (0x0C, True), # twelve + (2**32, True), # arbitrary + (2**64 - 2, True), # near-max + (2**64 - 1, True), # max + # TODO: Update t8n to un marshal > 64-bit int + # Exception: failed to evaluate: ERROR(10): failed un marshaling stdin + # (2**64, False), # overflow + # Exception: failed to evaluate: ERROR(10): failed un marshaling stdin + # (2**64 + 1, False), # overflow+1 + ], +) +@pytest.mark.valid_from("Cancun") +def test_beacon_root_precompile_timestamps( + state_test: StateTestFiller, + env: Environment, + pre: Dict, + tx: Transaction, + post: Dict, +): + """ + Tests the beacon root precompile call across for various valid and invalid timestamps. + + The expected result is that the precompile call will return the correct + `parent_beacon_block_root` for a valid input timestamp and return the zero'd 32 bytes value + for an invalid input timestamp. + """ + state_test( + env=env, + pre=pre, + txs=[tx], + post=post, + ) + + +@pytest.mark.parametrize( + "beacon_root, timestamp", + [ + (12, 12), # twelve + (2**32, 2**32), # arbitrary + (2**64 - 2, 2**64 - 2), # near-max + (2**64 - 1, 2**64 - 1), # max + ], + indirect=["beacon_root"], +) +@pytest.mark.valid_from("Cancun") +def test_beacon_root_equal_to_timestamp( + state_test: StateTestFiller, + env: Environment, + pre: Dict, + tx: Transaction, + post: Dict, +): + """ + Tests the beacon root precompile call where the beacon root is equal to the timestamp. + + The expected result is that the precompile call will return the `parent_beacon_block_root`, + as all timestamps used are valid. + """ + state_test( + env=env, + pre=pre, + txs=[tx], + post=post, + ) + + +@pytest.mark.parametrize( + "call_gas, valid_call", + [ + (BEACON_ROOT_PRECOMPILE_GAS, True), + (BEACON_ROOT_PRECOMPILE_GAS + 1, True), + (BEACON_ROOT_PRECOMPILE_GAS - 1, False), + ], +) +@pytest.mark.parametrize( + "timestamp", + [ + 12, # twelve + 2**32, # arbitrary + 2**64 - 2, # near-max + 2**64 - 1, # max + ], +) +@pytest.mark.valid_from("Cancun") +def test_beacon_root_timestamp_collisions( + blockchain_test: BlockchainTestFiller, + env: Environment, + pre: Dict, + tx: Transaction, + precompile_call_account: Account, + timestamp: int, + valid_call: bool, +): + """ + Tests multiple beacon root precompile calls where the timestamp index is calculated to + be equal for each call (i.e colliding). For each parameterized timestamp a list of + colliding timestamps are calculated using factors of the `HISTORY_ROOTS_MODULUS`. + + The expected result is that precompile call will return an equal beacon_root + for each timestamp used within the call, as the timestamp index used will be the same. + + Here we are predominantly testing that the `timestamp_index` and `root_index` are derived + correctly in the evm. + """ + post = {} + blocks, colliding_timestamps = [], [] + timestamp_collisions = 5 + for i in range(timestamp_collisions): + pre[to_address(0x100 + i)] = precompile_call_account + colliding_timestamps.append(timestamp + i * HISTORICAL_ROOTS_MODULUS) + + # check timestamp_index function is working as expected + timestamp_indexes = [timestamp_index(v) for v in colliding_timestamps] + assert len(set(timestamp_indexes)) == 1, "timestamp_index function is not working" + + for i, timestamp in enumerate(colliding_timestamps): + blocks.append( + Block( + txs=[ + tx.with_fields( + nonce=i, + to=to_address(0x100), + data=to_hash_bytes(timestamp), + ) + ], + beacon_root=DEFAULT_BEACON_ROOT_HASH, + ) + ) + post[to_address(0x100 + i)] = Account( + storage=expected_storage( + beacon_root=DEFAULT_BEACON_ROOT_HASH, + timestamp=timestamp, + valid_call=valid_call, + valid_input=True, + ) + ) + + blockchain_test( + genesis_environment=env, + pre=pre, + blocks=blocks, + post=post, + ) From c728cd05a5272c36d55098d22c7438b7d6c216ac Mon Sep 17 00:00:00 2001 From: spencer-tb Date: Thu, 27 Jul 2023 20:58:58 +0100 Subject: [PATCH 02/10] src/forks: Tox fix for Beacon Root functions. --- src/ethereum_test_forks/forks/forks.py | 11 +++++++++-- src/evm_transition_tool/tests/test_evaluate.py | 9 +++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/ethereum_test_forks/forks/forks.py b/src/ethereum_test_forks/forks/forks.py index 2e93501053..8f7230a094 100644 --- a/src/ethereum_test_forks/forks/forks.py +++ b/src/ethereum_test_forks/forks/forks.py @@ -71,7 +71,7 @@ def engine_new_payload_version( return None @classmethod - def header_beacon_root_required(cls, block_number: int, timestamp: int) -> bool: + def header_beacon_root_required(cls, block_number: int = 0, timestamp: int = 0) -> bool: """ At genesis, header must not contain parent beacon block root """ @@ -84,6 +84,13 @@ def engine_new_payload_blob_hashes(cls, block_number: int = 0, timestamp: int = """ return False + @classmethod + def engine_new_payload_beacon_root(cls, block_number: int = 0, timestamp: int = 0) -> bool: + """ + At genesis, payloads do not have a parent beacon block root. + """ + return False + @classmethod def get_reward(cls, block_number: int = 0, timestamp: int = 0) -> int: """ @@ -278,7 +285,7 @@ def header_blob_gas_used_required(cls, block_number: int = 0, timestamp: int = 0 return True @classmethod - def header_beacon_root_required(cls, block_number: int, timestamp: int) -> bool: + def header_beacon_root_required(cls, block_number: int = 0, timestamp: int = 0) -> bool: """ Parent beacon block root is required starting from Cancun. """ diff --git a/src/evm_transition_tool/tests/test_evaluate.py b/src/evm_transition_tool/tests/test_evaluate.py index 0513dc62a9..4a5c5c76c4 100644 --- a/src/evm_transition_tool/tests/test_evaluate.py +++ b/src/evm_transition_tool/tests/test_evaluate.py @@ -1,4 +1,4 @@ -import json +import json # noqa: D100 import os from pathlib import Path from shutil import which @@ -64,7 +64,7 @@ ), ], ) -def test_calc_state_root( +def test_calc_state_root( # noqa: D103 t8n: TransitionTool, fork: Fork, alloc: Dict, @@ -81,7 +81,7 @@ class TestEnv: @pytest.mark.parametrize("evm_tool", [GethTransitionTool]) @pytest.mark.parametrize("binary_arg", ["no_binary_arg", "path_type", "str_type"]) -def test_evm_tool_binary_arg(evm_tool, binary_arg): +def test_evm_tool_binary_arg(evm_tool, binary_arg): # noqa: D103 if binary_arg == "no_binary_arg": evm_tool().version() return @@ -100,7 +100,7 @@ def test_evm_tool_binary_arg(evm_tool, binary_arg): @pytest.mark.parametrize("t8n", [GethTransitionTool()]) @pytest.mark.parametrize("test_dir", os.listdir(path=FIXTURES_ROOT)) -def test_evm_t8n(t8n: TransitionTool, test_dir: str) -> None: +def test_evm_t8n(t8n: TransitionTool, test_dir: str) -> None: # noqa: D103 alloc_path = Path(FIXTURES_ROOT, test_dir, "alloc.json") txs_path = Path(FIXTURES_ROOT, test_dir, "txs.json") env_path = Path(FIXTURES_ROOT, test_dir, "env.json") @@ -125,5 +125,6 @@ def test_evm_t8n(t8n: TransitionTool, test_dir: str) -> None: ), ) print(result) + print(expected.get("result")) assert result_alloc == expected.get("alloc") assert result == expected.get("result") From 16aeb577b49fd33cfcb52d3bde211ed5f0e7f3f2 Mon Sep 17 00:00:00 2001 From: spencer-tb Date: Thu, 27 Jul 2023 21:54:42 +0100 Subject: [PATCH 03/10] tests/eip4788: Small changes. --- tests/cancun/eip4788_beacon_root/common.py | 2 +- tests/cancun/eip4788_beacon_root/test_beacon_root_precompile.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/cancun/eip4788_beacon_root/common.py b/tests/cancun/eip4788_beacon_root/common.py index 8e70eded09..a8e254088d 100644 --- a/tests/cancun/eip4788_beacon_root/common.py +++ b/tests/cancun/eip4788_beacon_root/common.py @@ -41,7 +41,7 @@ def expected_storage( - success or failure of the call - validity of the timestamp input used within the call """ - storage: Storage.StorageDictType = dict() + storage = Storage() # beacon root precompile call is successful if valid_call: storage[0] = 1 diff --git a/tests/cancun/eip4788_beacon_root/test_beacon_root_precompile.py b/tests/cancun/eip4788_beacon_root/test_beacon_root_precompile.py index b845f39ab2..cf2d2db8be 100644 --- a/tests/cancun/eip4788_beacon_root/test_beacon_root_precompile.py +++ b/tests/cancun/eip4788_beacon_root/test_beacon_root_precompile.py @@ -84,7 +84,7 @@ def test_beacon_root_precompile_calls( - `DELEGATECALL` - `CALLCODE` - `STATICCALL` - for diffent call gas amounts: + for different call gas amounts: - exact gas (valid call) - extra gas (valid call) - insufficient gas (invalid call) From 82647769be7029136f7b2b4f74744e60dde42d22 Mon Sep 17 00:00:00 2001 From: spencer-tb Date: Thu, 27 Jul 2023 22:03:56 +0100 Subject: [PATCH 04/10] src/types: Remove beaconRoot from engineNewPayload. --- src/ethereum_test_tools/common/types.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ethereum_test_tools/common/types.py b/src/ethereum_test_tools/common/types.py index 78c1c76d31..738a2eead9 100644 --- a/src/ethereum_test_tools/common/types.py +++ b/src/ethereum_test_tools/common/types.py @@ -2,7 +2,7 @@ Useful types for generating Ethereum tests. """ from copy import copy, deepcopy -from dataclasses import dataclass, fields +from dataclasses import dataclass, fields, replace from itertools import count from typing import ( Any, @@ -2482,7 +2482,9 @@ def from_fixture_header( new_payload = cls( payload=FixtureExecutionPayload.from_fixture_header( - header=header, transactions=transactions, withdrawals=withdrawals + header=replace(header, beacon_root=None), + transactions=transactions, + withdrawals=withdrawals, ), version=new_payload_version, error_code=error_code, From 4134599fd08884b6f3e0cfa427e5cea675fcd28a Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Sat, 29 Jul 2023 00:30:00 +0000 Subject: [PATCH 05/10] evm_transition_tool: set beacon root if required --- src/evm_transition_tool/transition_tool.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/evm_transition_tool/transition_tool.py b/src/evm_transition_tool/transition_tool.py index 2d500638f1..dd13883486 100644 --- a/src/evm_transition_tool/transition_tool.py +++ b/src/evm_transition_tool/transition_tool.py @@ -226,6 +226,14 @@ def calc_state_root(self, *, alloc: Any, fork: Fork, debug_output_path: str = "" if fork.header_withdrawals_required(0, 0): env["withdrawals"] = [] + if fork.header_excess_blob_gas_required(0, 0): + env["currentExcessBlobGas"] = "0" + + if fork.header_beacon_root_required(0, 0): + env[ + "beaconRoot" + ] = "0x0000000000000000000000000000000000000000000000000000000000000000" + _, result = self.evaluate( alloc=alloc, txs=[], @@ -268,6 +276,11 @@ def calc_withdrawals_root( if fork.header_excess_blob_gas_required(0, 0): env["currentExcessBlobGas"] = "0" + if fork.header_beacon_root_required(0, 0): + env[ + "beaconRoot" + ] = "0x0000000000000000000000000000000000000000000000000000000000000000" + _, result = self.evaluate( alloc={}, txs=[], From b82632e837f4fc5ebb7933a86555973ce61fae61 Mon Sep 17 00:00:00 2001 From: spencer-tb Date: Mon, 31 Jul 2023 11:03:30 +0100 Subject: [PATCH 06/10] tests/eip4788: Add HistoryStorageAddress with 1 wei to 4788 tests. --- tests/cancun/eip4788_beacon_root/conftest.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/cancun/eip4788_beacon_root/conftest.py b/tests/cancun/eip4788_beacon_root/conftest.py index 4003e19dae..a3b46b7ad4 100644 --- a/tests/cancun/eip4788_beacon_root/conftest.py +++ b/tests/cancun/eip4788_beacon_root/conftest.py @@ -8,6 +8,7 @@ from ethereum_test_tools import ( Account, Environment, + HistoryStorageAddress, TestAddress, Transaction, to_address, @@ -127,6 +128,7 @@ def pre( source account of all test transactions, and the precompile caller account. """ return { + HistoryStorageAddress: Account(balance=1), TestAddress: Account( nonce=0, balance=0x10**10, From 128bc8f7d15c001ad1a567d5a376ac734776cb90 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Mon, 31 Jul 2023 21:07:32 +0000 Subject: [PATCH 07/10] tools/spec: Fix beacon root on genesis --- src/ethereum_test_tools/spec/blockchain_test.py | 1 + src/ethereum_test_tools/spec/state_test.py | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/ethereum_test_tools/spec/blockchain_test.py b/src/ethereum_test_tools/spec/blockchain_test.py index 689ccc73f4..66b8247305 100644 --- a/src/ethereum_test_tools/spec/blockchain_test.py +++ b/src/ethereum_test_tools/spec/blockchain_test.py @@ -94,6 +94,7 @@ def make_genesis( if env.withdrawals is not None else None ), + beacon_root=Hash.or_none(env.beacon_root), ) genesis_rlp, genesis.hash = genesis.build( diff --git a/src/ethereum_test_tools/spec/state_test.py b/src/ethereum_test_tools/spec/state_test.py index ca32cf183d..36e129ec18 100644 --- a/src/ethereum_test_tools/spec/state_test.py +++ b/src/ethereum_test_tools/spec/state_test.py @@ -1,6 +1,7 @@ """ State test filler. """ +from copy import copy from dataclasses import dataclass from typing import Any, Callable, Dict, Generator, List, Mapping, Optional, Tuple, Type @@ -57,7 +58,13 @@ def make_genesis( """ Create a genesis block from the state test definition. """ - env = self.env.set_fork_requirements(fork) + env = copy(self.env) + + # Remove fields that should not be present in the genesis block. + env.withdrawals = None + env.beacon_root = None + + env = env.set_fork_requirements(fork) genesis = FixtureHeader( parent_hash=Hash(0), @@ -93,6 +100,7 @@ def make_genesis( if env.withdrawals is not None else None ), + beacon_root=Hash.or_none(env.beacon_root), ) genesis_rlp, genesis.hash = genesis.build( From 549f1541b1537096f5dc73741b0c4e01eac81a60 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Mon, 31 Jul 2023 21:08:05 +0000 Subject: [PATCH 08/10] tests/eip4788: fix tests --- .../eip4788_beacon_root/test_beacon_root_precompile.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/cancun/eip4788_beacon_root/test_beacon_root_precompile.py b/tests/cancun/eip4788_beacon_root/test_beacon_root_precompile.py index cf2d2db8be..99587b9bcb 100644 --- a/tests/cancun/eip4788_beacon_root/test_beacon_root_precompile.py +++ b/tests/cancun/eip4788_beacon_root/test_beacon_root_precompile.py @@ -184,8 +184,6 @@ def test_beacon_root_equal_to_timestamp( [ 12, # twelve 2**32, # arbitrary - 2**64 - 2, # near-max - 2**64 - 1, # max ], ) @pytest.mark.valid_from("Cancun") @@ -226,11 +224,12 @@ def test_beacon_root_timestamp_collisions( txs=[ tx.with_fields( nonce=i, - to=to_address(0x100), + to=to_address(0x100 + i), data=to_hash_bytes(timestamp), ) ], beacon_root=DEFAULT_BEACON_ROOT_HASH, + timestamp=timestamp, ) ) post[to_address(0x100 + i)] = Account( @@ -243,7 +242,6 @@ def test_beacon_root_timestamp_collisions( ) blockchain_test( - genesis_environment=env, pre=pre, blocks=blocks, post=post, From 6fdf31241242ae66366f620950d1adab2ddbc543 Mon Sep 17 00:00:00 2001 From: Spencer Taylor-Brown Date: Tue, 1 Aug 2023 10:30:48 +0100 Subject: [PATCH 09/10] Update src/ethereum_test_tools/common/types.py Co-authored-by: Mario Vega --- src/ethereum_test_tools/common/types.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ethereum_test_tools/common/types.py b/src/ethereum_test_tools/common/types.py index 738a2eead9..3b413afec6 100644 --- a/src/ethereum_test_tools/common/types.py +++ b/src/ethereum_test_tools/common/types.py @@ -2452,7 +2452,6 @@ class FixtureEngineNewPayload: json_encoder=JSONEncoder.Field( name="parentBeaconBlockRoot", cast_type=Hash, - to_json=True, ), ) error_code: Optional[EngineAPIError] = field( From 87267665ce91fb82344756c2ab6eef9775c0c950 Mon Sep 17 00:00:00 2001 From: spencer-tb Date: Tue, 1 Aug 2023 11:51:56 +0100 Subject: [PATCH 10/10] tests/eip4788: Add tx_to_address. --- tests/cancun/eip4788_beacon_root/conftest.py | 21 ++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/tests/cancun/eip4788_beacon_root/conftest.py b/tests/cancun/eip4788_beacon_root/conftest.py index a3b46b7ad4..7a5a8b11bf 100644 --- a/tests/cancun/eip4788_beacon_root/conftest.py +++ b/tests/cancun/eip4788_beacon_root/conftest.py @@ -53,7 +53,7 @@ def call_gas() -> int: # noqa: D103 @pytest.fixture -def call_address() -> str: # noqa: D103 +def caller_address() -> str: # noqa: D103 return to_address(0x100) @@ -98,7 +98,7 @@ def precompile_call_account(call_type: Op, call_gas: int) -> Account: return Account( nonce=0, code=precompile_call_code, - balance=10**10, + balance=0x10**10, ) @@ -121,7 +121,7 @@ def valid_input() -> bool: @pytest.fixture def pre( precompile_call_account: Account, - call_address: str, + caller_address: str, ) -> Dict: """ Prepares the pre state of all test cases, by setting the balance of the @@ -133,13 +133,18 @@ def pre( nonce=0, balance=0x10**10, ), - call_address: precompile_call_account, + caller_address: precompile_call_account, } +@pytest.fixture +def tx_to_address(request, caller_address: Account) -> bytes: # noqa: D103 + return request.param if hasattr(request, "param") else caller_address + + @pytest.fixture def tx( - call_address: str, + tx_to_address: str, timestamp: int, ) -> Transaction: """ @@ -149,7 +154,7 @@ def tx( ty=2, nonce=0, data=to_hash_bytes(timestamp), - to=call_address, + to=tx_to_address, value=0, gas_limit=1000000, max_fee_per_gas=7, @@ -159,7 +164,7 @@ def tx( @pytest.fixture def post( - call_address: str, + caller_address: str, beacon_root: bytes, timestamp: int, valid_call: bool, @@ -170,7 +175,7 @@ def post( failure of the call, and the validity of the timestamp input. """ return { - call_address: Account( + caller_address: Account( storage=expected_storage( beacon_root, timestamp,