From a64f8365b87d967e5b38673bf0e5382cb7c08b6b Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Wed, 4 Feb 2026 22:40:23 +0000 Subject: [PATCH 01/22] feat(testing/fill): Account-hash-based deploy addresses --- .../base_types/composite_types.py | 22 ++ .../base_types/tests/test_base_types.py | 56 ++++- .../plugins/filler/pre_alloc.py | 194 ++++++++---------- 3 files changed, 163 insertions(+), 109 deletions(-) diff --git a/packages/testing/src/execution_testing/base_types/composite_types.py b/packages/testing/src/execution_testing/base_types/composite_types.py index 1db6e9f7b2e..cdc38fd8d0e 100644 --- a/packages/testing/src/execution_testing/base_types/composite_types.py +++ b/packages/testing/src/execution_testing/base_types/composite_types.py @@ -1,5 +1,7 @@ """Base composite types for Ethereum test cases.""" +import hashlib +import json from dataclasses import dataclass from typing import ( Any, @@ -346,6 +348,15 @@ def canary(self) -> "Storage": ) +class FrozenStorage(Storage): + """Frozen storage.""" + + model_config = { + **Storage.model_config, + "frozen": True, + } + + class Account(CamelModel): """State associated with an address.""" @@ -367,6 +378,11 @@ class Account(CamelModel): state. """ + model_config = { + **CamelModel.model_config, + "frozen": True, + } + @dataclass(kw_only=True) class NonceMismatchError(Exception): """ @@ -514,6 +530,12 @@ def __bool__(self: "Account") -> bool: """Return True on a non-empty account.""" return any((self.nonce, self.balance, self.code, self.storage)) + def hash(self) -> Hash: + """Return the hash of the account given its properties.""" + data = self.model_dump(mode="json") + blob = json.dumps(data, sort_keys=True).encode("utf-8") + return Hash(hashlib.sha256(blob).digest()) + @classmethod def with_code(cls: Type, code: BytesConvertible) -> "Account": """Create account with provided `code` and nonce of `1`.""" diff --git a/packages/testing/src/execution_testing/base_types/tests/test_base_types.py b/packages/testing/src/execution_testing/base_types/tests/test_base_types.py index 3bd3551adf1..49ce901868b 100644 --- a/packages/testing/src/execution_testing/base_types/tests/test_base_types.py +++ b/packages/testing/src/execution_testing/base_types/tests/test_base_types.py @@ -6,7 +6,7 @@ from ..base_types import Address, Hash, Wei from ..base_types_json import to_json -from ..composite_types import AccessList +from ..composite_types import AccessList, Account @pytest.mark.parametrize( @@ -290,3 +290,57 @@ def test_json_deserialization( ) model_type = type(model_instance) assert model_type(**json) == model_instance + + +@pytest.mark.parametrize( + "account_1, account_2, equal", + [ + (Account(), Account(), True), + (Account(nonce=1), Account(nonce=2), False), + (Account(nonce=1), Account(nonce=1), True), + (Account(nonce=1), Account(nonce=1, code="0x1234"), False), + (Account(nonce=1, code="0x1234"), Account(nonce=1), False), + ( + Account(nonce=1, code="0x1234"), + Account(nonce=1, code="0x1234"), + True, + ), + ( + Account(nonce=1, code="0x1234"), + Account(nonce=1, code="0x5678"), + False, + ), + ( + Account(nonce=1, code="0x1234"), + Account(nonce=2, code="0x5678"), + False, + ), + ( + Account(nonce=1, code="0x1234"), + Account(nonce=2, code="0x1234"), + False, + ), + ( + Account(nonce=1, code="0x1234"), + Account(nonce=1, code="0x1234", storage={0: 0, 1: 1}), + False, + ), + ( + Account(nonce=1, code="0x1234", storage={1: 1, 0: 0}), + Account(nonce=1, code="0x1234", storage={0: 0, 1: 1}), + True, + ), + ], +) +def test_account_hash( + account_1: Account, account_2: Account, equal: bool +) -> None: + """Test two different accounts to return the same hash.""" + if equal: + assert account_1.hash() == account_2.hash(), ( + f"Account 1: {account_1.hash()}, Account 2: {account_2.hash()}" + ) + else: + assert account_1.hash() != account_2.hash(), ( + f"Account 1: {account_1.hash()}, Account 2: {account_2.hash()}" + ) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py index dd60772f9e7..b735bc3427d 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py @@ -4,8 +4,7 @@ from enum import IntEnum from functools import cache from hashlib import sha256 -from itertools import count -from typing import Any, Iterator, List, Literal +from typing import Any, Dict, List, Literal import pytest from pydantic import PrivateAttr @@ -20,7 +19,6 @@ StorageRootType, TestPrivateKey, TestPrivateKey2, - ZeroPaddedHexNumber, ) from execution_testing.base_types.conversions import ( BytesConvertible, @@ -88,6 +86,31 @@ class AllocMode(IntEnum): DELEGATION_DESIGNATION = b"\xef\x01\x00" +EMPTY_ACCOUNT_HASH = Account().hash() + + +def contract_address_from_account(account_hash: Hash, salt: int) -> Address: + """ + Calculate a deterministic address for a contract given the properties of + the account. + + Useful to not duplicate accounts in the pre-allocation when grouping + many tests. + """ + return Address( + Bytes(account_hash + salt.to_bytes(64, "big")).sha256()[12:] + ) + + +def eoa_from_account(account_hash: Hash, salt: int) -> EOA: + """ + Calculate a deterministic EOA for a contract given the properties of + the account. + + Useful to not duplicate accounts in the pre-allocation when grouping + many tests. + """ + return EOA(key=Bytes(account_hash + salt.to_bytes(64, "big")).sha256()) class Alloc(BaseAlloc): @@ -95,24 +118,20 @@ class Alloc(BaseAlloc): _eoa_fund_amount_default: int = PrivateAttr(10**21) _alloc_mode: AllocMode = PrivateAttr() - _contract_address_iterator: Iterator[Address] = PrivateAttr() - _eoa_iterator: Iterator[EOA] = PrivateAttr() + _account_salt: Dict[Hash, int] = PrivateAttr() _fork: Fork = PrivateAttr() def __init__( self, *args: Any, alloc_mode: AllocMode, - contract_address_iterator: Iterator[Address], - eoa_iterator: Iterator[EOA], fork: Fork, **kwargs: Any, ) -> None: """Initialize allocation with the given properties.""" super().__init__(*args, **kwargs) self._alloc_mode = alloc_mode - self._contract_address_iterator = contract_address_iterator - self._eoa_iterator = eoa_iterator + self._account_salt = {} self._fork = fork def __setitem__( @@ -125,6 +144,12 @@ def __setitem__( raise ValueError("Cannot set items in strict mode") super().__setitem__(address, account) + def get_next_account_salt(self, account_hash: Hash) -> int: + """Retrieve the next salt for this account.""" + salt = self._account_salt.get(account_hash, 0) + self._account_salt[account_hash] = salt + 1 + return salt + def code_pre_processor(self, code: BytesConvertible) -> BytesConvertible: """Pre-processes the code before setting it.""" return code @@ -231,12 +256,6 @@ def deploy_contract( 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, ( @@ -252,15 +271,26 @@ def deploy_contract( f"code too large: {len(code_bytes)} > {max_code_size}" ) - super().__setitem__( - contract_address, - Account( - nonce=nonce, - balance=balance, - code=code, - storage=storage, - ), + account = Account( + nonce=nonce, + balance=balance, + code=code, + storage=storage, ) + + if address is not None: + assert address not in self, ( + f"address {address} already in allocation" + ) + contract_address = address + else: + account_hash = account.hash() + salt = self.get_next_account_salt(account_hash) + contract_address = contract_address_from_account( + account_hash, salt + ) + + super().__setitem__(contract_address, account) if label is None: # Try to deduce the label from the code frame = inspect.currentframe() @@ -295,7 +325,6 @@ def fund_eoa( """ del label - eoa = next(self._eoa_iterator) if amount is None: amount = self._eoa_fund_amount_default if ( @@ -310,16 +339,20 @@ def fund_eoa( nonce=nonce, balance=amount, ) - if nonce > 0: - eoa.nonce = nonce else: # Type-4 transaction is sent to the EOA to set the storage, so # the nonce must be 1 - if ( - not isinstance(delegation, Address) - and delegation == "Self" - ): - delegation = eoa + code = b"" + if delegation is not None: + if ( + not isinstance(delegation, Address) + and delegation == "Self" + ): + # This is a placeholder value, since we don't know + # the address until the end of the function. + code = DELEGATION_DESIGNATION + b"Self" + else: + code = DELEGATION_DESIGNATION + delegation # If delegation is None but storage is not, realistically the # nonce should be 2 because the account must have delegated to # set the storage and then again to reset the delegation (but @@ -330,12 +363,22 @@ def fund_eoa( nonce=nonce, balance=amount, storage=storage if storage is not None else {}, - code=DELEGATION_DESIGNATION + bytes(delegation) - if delegation is not None - else b"", + code=code, ) - eoa.nonce = nonce + else: + account = Account() + + account_hash = account.hash() + salt = self.get_next_account_salt(account_hash) + eoa = eoa_from_account(account_hash, salt) + + if account.nonce > 0: + eoa.nonce = account.nonce + + if not isinstance(delegation, Address) and delegation == "Self": + account = account.copy(code=DELEGATION_DESIGNATION + eoa) + if account: super().__setitem__(eoa, account) return eoa @@ -352,20 +395,13 @@ def fund_address( If the address is already present in the pre-alloc the amount will be added to its existing balance. """ + del minimum_balance if address in self: - account = self[address] - if account is not None: - current_balance = account.balance or 0 - fund_amount = Number(amount) - if minimum_balance: - if current_balance >= fund_amount: - return - account.balance = ZeroPaddedHexNumber(fund_amount) - else: - account.balance = ZeroPaddedHexNumber( - current_balance + fund_amount - ) - return + raise Exception( + "Cannot fund an account already in state. " + "Use the appropriate `amount`, `balance` arguments " + "when creating the account." + ) super().__setitem__(address, Account(balance=amount)) def empty_account(self) -> Address: @@ -387,9 +423,8 @@ def empty_account(self) -> Address: Address: The address of the created empty account. """ - eoa = next(self._eoa_iterator) - - return Address(eoa) + salt = self.get_next_account_salt(EMPTY_ACCOUNT_HASH) + return Address(eoa_from_account(EMPTY_ACCOUNT_HASH, salt)) @pytest.fixture(scope="session") @@ -467,70 +502,15 @@ def node_id_for_entropy( raise Exception(f"Fixture format name not found in test {node_id}") -@pytest.fixture(scope="function") -def contract_address_iterator( - request: pytest.FixtureRequest, - contract_start_address: int, - contract_address_increments: int, - node_id_for_entropy: str, -) -> Iterator[Address]: - """Return iterator over contract addresses with dynamic scoping.""" - if request.config.getoption( - # TODO: Ideally, we should check the fixture format instead of checking - # parameters. - "generate_pre_alloc_groups", - default=False, - ) or request.config.getoption("use_pre_alloc_groups", default=False): - # Use a starting address that is derived from the test node - contract_start_address = sha256_from_string(node_id_for_entropy) - return iter( - Address( - (contract_start_address + (i * contract_address_increments)) - % 2**160 - ) - for i in count() - ) - - @cache def eoa_by_index(i: int) -> EOA: """Return EOA by index.""" return EOA(key=TestPrivateKey + i if i != 1 else TestPrivateKey2, nonce=0) -@pytest.fixture(scope="function") -def eoa_iterator( - request: pytest.FixtureRequest, - node_id_for_entropy: str, -) -> Iterator[EOA]: - """Return iterator over EOAs copies with dynamic scoping.""" - if request.config.getoption( - # TODO: Ideally, we should check the fixture format instead of checking - # parameters. - "generate_pre_alloc_groups", - default=False, - ) or request.config.getoption("use_pre_alloc_groups", default=False): - # Use a starting address that is derived from the test node - eoa_start_pk = sha256_from_string(node_id_for_entropy) - # secp256k1 curve order constant - curve_order = ( # noqa: E501 - 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - ) - return iter( - EOA( - key=(eoa_start_pk + i) % curve_order, - nonce=0, - ) - for i in count() - ) - return iter(eoa_by_index(i).copy() for i in count()) - - @pytest.fixture(scope="function") def pre( alloc_mode: AllocMode, - contract_address_iterator: Iterator[Address], - eoa_iterator: Iterator[EOA], fork: Fork | None, request: pytest.FixtureRequest, ) -> Alloc: @@ -543,7 +523,5 @@ def pre( return Alloc( alloc_mode=alloc_mode, - contract_address_iterator=contract_address_iterator, - eoa_iterator=eoa_iterator, fork=actual_fork, ) From 66af980c1a8dbacf5ac8139e3b9a85e33f0848ac Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Thu, 5 Feb 2026 01:55:28 +0000 Subject: [PATCH 02/22] fix(testing): Allow EOAs to have code for collisions --- .../cli/pytest_commands/plugins/execute/pre_alloc.py | 7 ++++--- .../cli/pytest_commands/plugins/filler/pre_alloc.py | 8 ++++++++ .../src/execution_testing/test_types/account_types.py | 1 + 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py index 3ead061db10..675ed6d094c 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py @@ -20,7 +20,6 @@ Number, Storage, StorageRootType, - ZeroPaddedHexNumber, ) from execution_testing.base_types.conversions import ( BytesConvertible, @@ -578,6 +577,7 @@ def fund_eoa( amount: NumberConvertible | None = None, label: str | None = None, storage: Storage | StorageRootType | None = None, + code: BytesConvertible | None = None, delegation: Address | Literal["Self"] | None = None, nonce: NumberConvertible | None = None, ) -> EOA: @@ -586,6 +586,7 @@ def fund_eoa( by `amount`. """ assert nonce is None, "nonce parameter is not supported for execute" + assert code is None, "code parameter is not supported for execute" eoa = next(self._eoa_iterator) eoa.label = label amount_str = ( @@ -743,7 +744,7 @@ def fund_address( if address in self: account = self[address] if account is not None: - account.balance = ZeroPaddedHexNumber(current_balance) + self[address] = account.copy(balance=current_balance) else: super().__setitem__( address, Account(balance=current_balance) @@ -778,7 +779,7 @@ def fund_address( if address in self: account = self[address] if account is not None: - account.balance = ZeroPaddedHexNumber(new_balance) + self[address] = account.copy(balance=new_balance) cur_eth = current_balance / 10**18 new_eth = new_balance / 10**18 logger.debug( diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py index b735bc3427d..b4cbbd73dcb 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py @@ -313,6 +313,7 @@ def fund_eoa( amount: NumberConvertible | None = None, label: str | None = None, storage: Storage | None = None, + code: BytesConvertible | None = None, delegation: Address | Literal["Self"] | None = None, nonce: NumberConvertible | None = None, ) -> EOA: @@ -330,9 +331,14 @@ def fund_eoa( if ( Number(amount) > 0 or storage is not None + or code is not None or delegation is not None or (nonce is not None and Number(nonce) > 0) ): + if code is not None and delegation is not None: + raise Exception( + "code and delegation cannot be set at the same time" + ) if storage is None and delegation is None: nonce = Number(0 if nonce is None else nonce) account = Account( @@ -353,6 +359,8 @@ def fund_eoa( code = DELEGATION_DESIGNATION + b"Self" else: code = DELEGATION_DESIGNATION + delegation + elif code is not None: + code = Bytes(code) # If delegation is None but storage is not, realistically the # nonce should be 2 because the account must have delegated to # set the storage and then again to reset the delegation (but diff --git a/packages/testing/src/execution_testing/test_types/account_types.py b/packages/testing/src/execution_testing/test_types/account_types.py index 04106c40059..e66fc463fe7 100644 --- a/packages/testing/src/execution_testing/test_types/account_types.py +++ b/packages/testing/src/execution_testing/test_types/account_types.py @@ -442,6 +442,7 @@ def fund_eoa( amount: NumberConvertible | None = None, label: str | None = None, storage: Storage | None = None, + code: BytesConvertible | None = None, delegation: Address | Literal["Self"] | None = None, nonce: NumberConvertible | None = None, ) -> EOA: From 95bfc9158a2a00fe5e41d1e391d9958ae6181fe3 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Thu, 5 Feb 2026 01:56:28 +0000 Subject: [PATCH 03/22] fix(tests): All tests using pre.fund_address --- tests/berlin/eip2930_access_list/test_acl.py | 1 - .../test_contract_deployment.py | 1 - tests/prague/eip7251_consolidations/test_contract_deployment.py | 1 - tests/prague/eip7702_set_code_tx/test_set_code_txs.py | 2 -- 4 files changed, 5 deletions(-) diff --git a/tests/berlin/eip2930_access_list/test_acl.py b/tests/berlin/eip2930_access_list/test_acl.py index d96c520537c..982d660c453 100644 --- a/tests/berlin/eip2930_access_list/test_acl.py +++ b/tests/berlin/eip2930_access_list/test_acl.py @@ -233,7 +233,6 @@ def test_transaction_intrinsic_gas_cost( ) sender = pre.fund_eoa() tx_value = 1 - pre.fund_address(sender, tx_value) contract_creation = False tx_data = b"" diff --git a/tests/prague/eip7002_el_triggerable_withdrawals/test_contract_deployment.py b/tests/prague/eip7002_el_triggerable_withdrawals/test_contract_deployment.py index f7160a6ea68..98e5b83267d 100644 --- a/tests/prague/eip7002_el_triggerable_withdrawals/test_contract_deployment.py +++ b/tests/prague/eip7002_el_triggerable_withdrawals/test_contract_deployment.py @@ -49,7 +49,6 @@ def test_system_contract_deployment( fee=Spec.get_fee(0), source_address=sender, ) - pre.fund_address(sender, withdrawal_request.value) intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() test_transaction_gas = intrinsic_gas_calculator( calldata=withdrawal_request.calldata diff --git a/tests/prague/eip7251_consolidations/test_contract_deployment.py b/tests/prague/eip7251_consolidations/test_contract_deployment.py index b1e14f8ef91..4bedc48f4c7 100644 --- a/tests/prague/eip7251_consolidations/test_contract_deployment.py +++ b/tests/prague/eip7251_consolidations/test_contract_deployment.py @@ -51,7 +51,6 @@ def test_system_contract_deployment( fee=Spec.get_fee(0), source_address=sender, ) - pre.fund_address(sender, consolidation_request.value) intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() test_transaction_gas = intrinsic_gas_calculator( calldata=consolidation_request.calldata diff --git a/tests/prague/eip7702_set_code_tx/test_set_code_txs.py b/tests/prague/eip7702_set_code_tx/test_set_code_txs.py index b52d383a54e..e9b0d387b30 100644 --- a/tests/prague/eip7702_set_code_tx/test_set_code_txs.py +++ b/tests/prague/eip7702_set_code_tx/test_set_code_txs.py @@ -3094,8 +3094,6 @@ def test_set_code_to_system_contract( ) caller_code_address = pre.deploy_contract(caller_code) sender = pre.fund_eoa() - if call_value > 0: - pre.fund_address(sender, call_value) txs = [ Transaction( From 020b26b8f3235d10e962eaa4b596ffdcbf7ec16e Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Thu, 5 Feb 2026 01:56:39 +0000 Subject: [PATCH 04/22] fix(tests): Tests setting EOA code --- tests/prague/eip7702_set_code_tx/test_gas.py | 42 +++++++++++++++---- .../eip7702_set_code_tx/test_set_code_txs.py | 10 ++--- 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/tests/prague/eip7702_set_code_tx/test_gas.py b/tests/prague/eip7702_set_code_tx/test_gas.py index 27cd2725efe..a3ff3b1d076 100644 --- a/tests/prague/eip7702_set_code_tx/test_gas.py +++ b/tests/prague/eip7702_set_code_tx/test_gas.py @@ -22,7 +22,6 @@ BalNonceChange, BlockAccessListExpectation, Bytecode, - Bytes, ChainConfig, CodeGasMeasure, Fork, @@ -181,10 +180,7 @@ def generator( assert not self_sponsored or i > 0, ( "Self-sponsored contract authority is not supported" ) - authority = pre.fund_eoa() - authority_account = pre[authority] - assert authority_account is not None - authority_account.code = Bytes(Op.STOP) + authority = pre.fund_eoa(code=Op.STOP) yield AuthorityWithProperties( authority=authority, address_type=current_authority_type, @@ -726,7 +722,14 @@ def gas_test_parameter_args( { "authority_type": AddressType.CONTRACT, }, - marks=pytest.mark.pre_alloc_modify, + marks=[ + pytest.mark.pre_alloc_modify, + pytest.mark.execute( + pytest.mark.skip( + reason="Requires contract-eoa address collision" + ) + ), + ], id="single_valid_authorization_invalid_contract_authority", ), pytest.param( @@ -738,7 +741,14 @@ def gas_test_parameter_args( ], "authorizations_count": multiple_authorizations_count, }, - marks=pytest.mark.pre_alloc_modify, + marks=[ + pytest.mark.pre_alloc_modify, + pytest.mark.execute( + pytest.mark.skip( + reason="Requires contract-eoa address collision" + ) + ), + ], id="multiple_authorizations_empty_account_then_contract_authority", ), pytest.param( @@ -747,7 +757,14 @@ def gas_test_parameter_args( "authority_type": [AddressType.EOA, AddressType.CONTRACT], "authorizations_count": multiple_authorizations_count, }, - marks=pytest.mark.pre_alloc_modify, + marks=[ + pytest.mark.pre_alloc_modify, + pytest.mark.execute( + pytest.mark.skip( + reason="Requires contract-eoa address collision" + ) + ), + ], id="multiple_authorizations_eoa_then_contract_authority", ), pytest.param( @@ -757,7 +774,14 @@ def gas_test_parameter_args( "authority_type": [AddressType.EOA, AddressType.CONTRACT], "authorizations_count": multiple_authorizations_count, }, - marks=pytest.mark.pre_alloc_modify, + marks=[ + pytest.mark.pre_alloc_modify, + pytest.mark.execute( + pytest.mark.skip( + reason="Requires contract-eoa address collision" + ) + ), + ], id="multiple_authorizations_eoa_self_sponsored_then_contract_authority", ), ] diff --git a/tests/prague/eip7702_set_code_tx/test_set_code_txs.py b/tests/prague/eip7702_set_code_tx/test_set_code_txs.py index e9b0d387b30..78ef3b8e2e8 100644 --- a/tests/prague/eip7702_set_code_tx/test_set_code_txs.py +++ b/tests/prague/eip7702_set_code_tx/test_set_code_txs.py @@ -4011,7 +4011,9 @@ def test_set_code_from_account_with_non_delegating_code( delegating) But at the same time it has auth tuple that will point this sender account To be eoa, delegation, contract .. etc """ - sender = pre.fund_eoa(nonce=1) + # Set the sender account to have some code, that is specifically not a + # delegation. + sender = pre.fund_eoa(nonce=1, code=Op.STOP) random_address = pre.fund_eoa(0) set_code_to_address: Address @@ -4029,12 +4031,6 @@ def test_set_code_from_account_with_non_delegating_code( raise ValueError(f"Unsupported set code type: {set_code_type}") callee_address = pre.deploy_contract(Op.SSTORE(0, 1) + Op.STOP) - # Set the sender account to have some code, that is specifically not a - # delegation. - sender_account = pre[sender] - assert sender_account is not None - sender_account.code = Bytes(Op.STOP) - tx = Transaction( gas_limit=100_000, to=callee_address, From 8db9f78f113af004e043616e7d8cf07e46042511 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Fri, 6 Feb 2026 23:39:31 +0000 Subject: [PATCH 05/22] fixes --- .../plugins/execute/execute.py | 2 +- .../plugins/execute/pre_alloc.py | 157 ++++--- .../plugins/filler/pre_alloc.py | 196 ++------- .../plugins/filler/tests/test_pre_alloc.py | 300 ++++++++----- .../plugins/shared/execute_fill.py | 93 +++- .../plugins/shared/pre_alloc.py | 398 ++++++++++++++++++ .../specs/static_state/state_static.py | 2 +- 7 files changed, 813 insertions(+), 335 deletions(-) create mode 100644 packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/pre_alloc.py diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute.py index 1bdde105396..73e93d09262 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute.py @@ -812,7 +812,7 @@ def pytest_collection_modifyitems( elif marker.name == "valid_at_transition_to": items_for_removal.append(i) continue - elif marker.name == "pre_alloc_modify": + elif marker.name == "pre_alloc_mutable": item.add_marker( pytest.mark.skip( reason="Pre-alloc modification not supported" diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py index 675ed6d094c..884e595b4e8 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py @@ -23,7 +23,6 @@ ) from execution_testing.base_types.conversions import ( BytesConvertible, - FixedSizeBytesConvertible, NumberConvertible, ) from execution_testing.forks import Fork @@ -39,10 +38,11 @@ TransactionTestMetadata, compute_deterministic_create2_address, ) -from execution_testing.test_types import Alloc as BaseAlloc from execution_testing.tools import Initcode from execution_testing.vm import Bytecode, Op +from ..shared.pre_alloc import Alloc as BaseAlloc +from ..shared.pre_alloc import AllocFlags from .contracts import ( check_deterministic_factory_deployment, deploy_deterministic_factory_contract, @@ -228,7 +228,6 @@ class PendingTransaction(Transaction): class Alloc(BaseAlloc): """A custom class that inherits from the original Alloc class.""" - _fork: Fork = PrivateAttr() _sender: EOA = PrivateAttr() _eth_rpc: EthRPC = PrivateAttr() _pending_txs: List[PendingTransaction] = PrivateAttr(default_factory=list) @@ -243,7 +242,6 @@ class Alloc(BaseAlloc): def __init__( self, *args: Any, - fork: Fork, sender: EOA, eth_rpc: EthRPC, eoa_iterator: Iterator[EOA], @@ -254,7 +252,6 @@ def __init__( ) -> None: """Initialize the pre-alloc with the given parameters.""" super().__init__(*args, **kwargs) - self._fork = fork self._sender = sender self._eth_rpc = eth_rpc self._eoa_iterator = eoa_iterator @@ -262,16 +259,6 @@ def __init__( self._node_id = node_id self._address_stubs = address_stubs or AddressStubs(root={}) - def __setitem__( - self, - address: Address | FixedSizeBytesConvertible, - account: Account | None, - ) -> None: - """Set account associated with an address.""" - raise ValueError( - "Tests are not allowed to set pre-alloc items in execute mode" - ) - def code_pre_processor(self, code: Bytecode) -> Bytecode: """Pre-processes the code before setting it.""" return code @@ -302,18 +289,18 @@ def _add_pending_tx( self._pending_txs.append(pending_tx) return pending_tx - def deterministic_deploy_contract( + def _deterministic_deploy_contract( self, *, deploy_code: BytesConvertible, - salt: Hash | int = 0, - initcode: BytesConvertible | None = None, - storage: Storage | StorageRootType | None = None, - label: str | None = None, + salt: Hash | int, + initcode: BytesConvertible | None, + storage: Storage | StorageRootType | None, + label: str | None, ) -> Address: """ - Deploy a contract to the allocation at a deterministic location - using a deterministic deployment proxy. + Execute implementation of contract deployment to a deterministic + location. """ del storage gas_costs = self._fork.gas_costs() @@ -409,7 +396,7 @@ def deterministic_deploy_contract( balance = self._eth_rpc.get_balance(contract_address) nonce = self._eth_rpc.get_transaction_count(contract_address) - super().__setitem__( + self.__internal_setitem__( contract_address, Account( nonce=nonce, @@ -422,18 +409,18 @@ def deterministic_deploy_contract( contract_address.label = label return contract_address - def deploy_contract( + def _deploy_contract( self, code: BytesConvertible, *, - storage: Storage | StorageRootType | None = None, - balance: NumberConvertible = 0, - nonce: NumberConvertible = 1, - address: Address | None = None, - label: str | None = None, - stub: str | None = None, + storage: Storage | StorageRootType | None, + balance: NumberConvertible, + nonce: NumberConvertible, + address: Address | None, + label: str | None, + stub: str | None, ) -> Address: - """Deploy a contract to the allocation.""" + """Execute implementation of contract deployment.""" if storage is None: storage = {} assert address is None, "address parameter is not supported" @@ -471,7 +458,7 @@ def deploy_contract( f"Stub contract {contract_address}: balance={bal_eth:.18f} " f"ETH, nonce={nonce}, code_size={len(code)} bytes" ) - super().__setitem__( + self.__internal_setitem__( contract_address, Account( nonce=nonce, @@ -559,7 +546,7 @@ def deploy_contract( "impossible to deploy contract with nonce lower than one" ) - super().__setitem__( + self.__internal_setitem__( contract_address, Account( nonce=nonce, @@ -572,18 +559,17 @@ def deploy_contract( contract_address.label = label return contract_address - def fund_eoa( + def _fund_eoa( self, - amount: NumberConvertible | None = None, - label: str | None = None, - storage: Storage | StorageRootType | None = None, - code: BytesConvertible | None = None, - delegation: Address | Literal["Self"] | None = None, - nonce: NumberConvertible | None = None, + amount: NumberConvertible | None, + label: str | None, + storage: Storage | StorageRootType | None, + code: BytesConvertible | None, + delegation: Address | Literal["Self"] | None, + nonce: NumberConvertible | None, ) -> EOA: """ - Add a previously unused EOA to the pre-alloc with the balance specified - by `amount`. + Execute implementation of EOA funding. """ assert nonce is None, "nonce parameter is not supported for execute" assert code is None, "code parameter is not supported for execute" @@ -703,7 +689,7 @@ def fund_eoa( if amount is not None: account_kwargs["balance"] = amount account = Account(**account_kwargs) - super().__setitem__(eoa, account) + self.__internal_setitem__(eoa, account) self._funded_eoa.append(eoa) balance_str = ( f"{Number(amount) / 10**18:.18f} ETH" @@ -716,18 +702,15 @@ def fund_eoa( ) return eoa - def fund_address( + def _fund_address( self, address: Address, amount: NumberConvertible, *, - minimum_balance: bool = False, + minimum_balance: bool, ) -> None: """ - 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. + Execute implementation of address funding. """ current_balance = self._eth_rpc.get_balance(address) fund_amount = int(Number(amount)) @@ -746,7 +729,7 @@ def fund_address( if account is not None: self[address] = account.copy(balance=current_balance) else: - super().__setitem__( + self.__internal_setitem__( address, Account(balance=current_balance) ) return @@ -787,38 +770,25 @@ def fund_address( f"{cur_eth:.18f} ETH -> {new_eth:.18f} ETH" ) else: - super().__setitem__(address, Account(balance=new_balance)) + self.__internal_setitem__( + address, Account(balance=new_balance) + ) else: - super().__setitem__(address, Account(balance=new_balance)) + self.__internal_setitem__(address, Account(balance=new_balance)) logger.info( f"Address {address} funding tx created (label={address.label}): " f"{Number(amount) / 10**18:.18f} ETH" ) - def empty_account(self) -> Address: + def _empty_account(self) -> Address: """ - Add a previously unused account guaranteed to be empty to the - pre-alloc. - - This ensures the account has: - - Zero balance - - Zero nonce - - No code - - No storage - - This is different from precompiles or system contracts. The function - does not send any transactions, ensuring that the account remains - "empty." - - Returns: - Address: The address of the created empty account. - + Execute implementation of empty account creation. """ eoa = next(self._eoa_iterator) logger.debug(f"Creating empty account at {eoa}") - super().__setitem__( + self.__internal_setitem__( eoa, Account( nonce=0, @@ -914,9 +884,55 @@ def send_pending_transactions(self) -> List[TransactionByHashResponse]: return responses +@pytest.fixture(scope="function") +def alloc_flags( + alloc_flags_from_test_markers: AllocFlags, +) -> AllocFlags: + """ + Verify this test does not require flags that are unsupported by execute. + + Otherwise skip. + """ + if ( + AllocFlags.ALLOW_ADDRESS_SET_TO_ACCOUNT + in alloc_flags_from_test_markers + ): + pytest.skip( + "Execute mode does not allow setting an address directly in the " + "pre-alloc." + ) + + if ( + AllocFlags.ALLOW_DEPLOY_TO_HARDCODED_ADDRESS + in alloc_flags_from_test_markers + ): + pytest.skip( + "Execute mode does not allow deploying to a hardcoded address." + ) + + if AllocFlags.ALLOW_ZERO_NONCE_CONTRACTS in alloc_flags_from_test_markers: + pytest.skip( + "Execute mode does not allow deploying contracts with zero nonce." + ) + + if AllocFlags.ALLOW_EOA_WITH_CODE in alloc_flags_from_test_markers: + pytest.skip("Execute mode does not allow creating EOAs with code.") + + if ( + AllocFlags.ALLOW_EOA_WITH_HARDCODED_NONCE + in alloc_flags_from_test_markers + ): + pytest.skip( + "Execute mode does not allow creating EOAs with a hardcoded nonce." + ) + + return alloc_flags_from_test_markers + + @pytest.fixture(autouse=True, scope="function") def pre( fork: Fork, + alloc_flags: AllocFlags, worker_key: EOA, eoa_iterator: Iterator[EOA], eth_rpc: EthRPC, @@ -942,6 +958,7 @@ def pre( ) pre = Alloc( fork=actual_fork, + flags=alloc_flags, sender=worker_key, eth_rpc=eth_rpc, eoa_iterator=eoa_iterator, diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py index b4cbbd73dcb..26654c3b99d 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py @@ -1,10 +1,9 @@ """Pre-alloc specifically conditioned for test filling.""" import inspect -from enum import IntEnum from functools import cache from hashlib import sha256 -from typing import Any, Dict, List, Literal +from typing import Dict, List, Literal import pytest from pydantic import PrivateAttr @@ -22,7 +21,6 @@ ) from execution_testing.base_types.conversions import ( BytesConvertible, - FixedSizeBytesConvertible, NumberConvertible, ) from execution_testing.fixtures import LabeledFixtureFormat @@ -34,11 +32,10 @@ EOA, compute_deterministic_create2_address, ) -from execution_testing.test_types import Alloc as BaseAlloc from execution_testing.tools import Initcode -CONTRACT_START_ADDRESS_DEFAULT = 0x1000000000000000000000000000000000001000 -CONTRACT_ADDRESS_INCREMENTS_DEFAULT = 0x100 +from ..shared.pre_alloc import Alloc as BaseAlloc +from ..shared.pre_alloc import AllocFlags def pytest_addoption(parser: pytest.Parser) -> None: @@ -48,41 +45,8 @@ def pytest_addoption(parser: pytest.Parser) -> None: "Arguments defining pre-allocation behavior during test filling.", ) - pre_alloc_group.addoption( - "--strict-alloc", - action="store_true", - dest="strict_alloc", - default=False, - help=( - "[DEBUG ONLY] Disallows deploying a contract in a predefined " - "address." - ), - ) - pre_alloc_group.addoption( - "--ca-start", - "--contract-address-start", - action="store", - dest="test_contract_start_address", - default=f"{CONTRACT_START_ADDRESS_DEFAULT}", - type=str, - help="Starting address from which tests will deploy contracts.", - ) - pre_alloc_group.addoption( - "--ca-incr", - "--contract-address-increment", - action="store", - dest="test_contract_address_increments", - default=f"{CONTRACT_ADDRESS_INCREMENTS_DEFAULT}", - type=str, - help="Address increment value for each deployed contract by a test.", - ) - - -class AllocMode(IntEnum): - """Allocation mode for the state.""" - - PERMISSIVE = 0 - STRICT = 1 + # No options for now + del pre_alloc_group DELEGATION_DESIGNATION = b"\xef\x01\x00" @@ -117,32 +81,7 @@ class Alloc(BaseAlloc): """Allocation of accounts in the state, pre and post test execution.""" _eoa_fund_amount_default: int = PrivateAttr(10**21) - _alloc_mode: AllocMode = PrivateAttr() - _account_salt: Dict[Hash, int] = PrivateAttr() - _fork: Fork = PrivateAttr() - - def __init__( - self, - *args: Any, - alloc_mode: AllocMode, - fork: Fork, - **kwargs: Any, - ) -> None: - """Initialize allocation with the given properties.""" - super().__init__(*args, **kwargs) - self._alloc_mode = alloc_mode - self._account_salt = {} - self._fork = fork - - def __setitem__( - self, - address: Address | FixedSizeBytesConvertible, - account: Account | None, - ) -> None: - """Set account associated with an address.""" - if self._alloc_mode == AllocMode.STRICT: - raise ValueError("Cannot set items in strict mode") - super().__setitem__(address, account) + _account_salt: Dict[Hash, int] = PrivateAttr(default_factory=dict) def get_next_account_salt(self, account_hash: Hash) -> int: """Retrieve the next salt for this account.""" @@ -154,18 +93,18 @@ def code_pre_processor(self, code: BytesConvertible) -> BytesConvertible: """Pre-processes the code before setting it.""" return code - def deterministic_deploy_contract( + def _deterministic_deploy_contract( self, *, deploy_code: BytesConvertible, - salt: Hash | int = 0, - initcode: BytesConvertible | None = None, - storage: Storage | StorageRootType | None = None, - label: str | None = None, + salt: Hash | int, + initcode: BytesConvertible | None, + storage: Storage | StorageRootType | None, + label: str | None, ) -> Address: """ - Deploy a contract to the allocation at a deterministic location - using a deterministic deployment proxy. + Filler implementation of contract deployment to a deterministic + location. """ if not isinstance(deploy_code, Bytes): deploy_code = Bytes(deploy_code) @@ -196,7 +135,7 @@ def deterministic_deploy_contract( fork_deterministic_factory_address is None and DETERMINISTIC_FACTORY_ADDRESS not in self ): - super().__setitem__( + self.__internal_setitem__( DETERMINISTIC_FACTORY_ADDRESS, Account( nonce=1, @@ -205,7 +144,7 @@ def deterministic_deploy_contract( ), ) - super().__setitem__( + self.__internal_setitem__( contract_address, Account( nonce=1, @@ -230,38 +169,24 @@ def deterministic_deploy_contract( contract_address.label = label return contract_address - def deploy_contract( + def _deploy_contract( self, code: BytesConvertible, *, - storage: Storage | StorageRootType | None = None, - balance: NumberConvertible = 0, - nonce: NumberConvertible = 1, - address: Address | None = None, - label: str | None = None, - stub: str | None = None, + storage: Storage | StorageRootType | None, + balance: NumberConvertible, + nonce: NumberConvertible, + address: Address | None, + label: str | None, + stub: str | 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! + Filler implementation of contract deployment. """ del stub if storage is None: storage = {} - if address is not None: - assert self._alloc_mode == AllocMode.PERMISSIVE, ( - "address parameter is not supported" - ) - - if self._alloc_mode == AllocMode.STRICT: - assert Number(nonce) >= 1, ( - "impossible to deploy contract with nonce lower than one" - ) - code = self.code_pre_processor(code) code_bytes = ( bytes(code) if not isinstance(code, (bytes, str)) else code @@ -290,7 +215,7 @@ def deploy_contract( account_hash, salt ) - super().__setitem__(contract_address, account) + self.__internal_setitem__(contract_address, account) if label is None: # Try to deduce the label from the code frame = inspect.currentframe() @@ -308,18 +233,17 @@ def deploy_contract( contract_address.label = label return contract_address - def fund_eoa( + def _fund_eoa( self, - amount: NumberConvertible | None = None, - label: str | None = None, - storage: Storage | None = None, - code: BytesConvertible | None = None, - delegation: Address | Literal["Self"] | None = None, - nonce: NumberConvertible | None = None, + amount: NumberConvertible | None, + label: str | None, + storage: Storage | None, + code: BytesConvertible | None, + delegation: Address | Literal["Self"] | None, + nonce: NumberConvertible | None, ) -> EOA: """ - Add a previously unused EOA to the pre-alloc with the balance specified - by `amount`. + Filler implementation of EOA funding. If amount is 0, nothing will be added to the pre-alloc but a new and unique EOA will be returned. @@ -387,21 +311,18 @@ def fund_eoa( if not isinstance(delegation, Address) and delegation == "Self": account = account.copy(code=DELEGATION_DESIGNATION + eoa) if account: - super().__setitem__(eoa, account) + self.__internal_setitem__(eoa, account) return eoa - def fund_address( + def _fund_address( self, address: Address, amount: NumberConvertible, *, - minimum_balance: bool = False, + minimum_balance: bool, ) -> None: """ - 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. + Filler implementation of address funding. """ del minimum_balance if address in self: @@ -410,51 +331,16 @@ def fund_address( "Use the appropriate `amount`, `balance` arguments " "when creating the account." ) - super().__setitem__(address, Account(balance=amount)) + self.__internal_setitem__(address, Account(balance=amount)) - def empty_account(self) -> Address: + def _empty_account(self) -> Address: """ - Add a previously unused account guaranteed to be empty to the - pre-alloc. - - This ensures the account has: - - Zero balance - - Zero nonce - - No code - - No storage - - This is different from precompiles or system contracts. The function - does not send any transactions, ensuring that the account remains - "empty." - - Returns: - Address: The address of the created empty account. - + Filler implementation of empty account creation. """ salt = self.get_next_account_salt(EMPTY_ACCOUNT_HASH) return Address(eoa_from_account(EMPTY_ACCOUNT_HASH, salt)) -@pytest.fixture(scope="session") -def alloc_mode(request: pytest.FixtureRequest) -> AllocMode: - """Return allocation mode for the tests.""" - if request.config.getoption("strict_alloc"): - return AllocMode.STRICT - return AllocMode.PERMISSIVE - - -@pytest.fixture(scope="session") -def contract_start_address(request: pytest.FixtureRequest) -> int: - """Return starting address for contract deployment.""" - return int(request.config.getoption("test_contract_start_address"), 0) - - -@pytest.fixture(scope="session") -def contract_address_increments(request: pytest.FixtureRequest) -> int: - """Return address increment for contract deployment.""" - return int(request.config.getoption("test_contract_address_increments"), 0) - - def sha256_from_string(s: str) -> int: """Return SHA-256 hash of a string.""" return int.from_bytes(sha256(s.encode("utf-8")).digest(), "big") @@ -518,7 +404,7 @@ def eoa_by_index(i: int) -> EOA: @pytest.fixture(scope="function") def pre( - alloc_mode: AllocMode, + alloc_flags: AllocFlags, fork: Fork | None, request: pytest.FixtureRequest, ) -> Alloc: @@ -530,6 +416,6 @@ def pre( actual_fork = request.node.fork return Alloc( - alloc_mode=alloc_mode, + flags=alloc_flags, fork=actual_fork, ) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_pre_alloc.py index 779dedb353d..1c795f44134 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_pre_alloc.py @@ -1,109 +1,124 @@ """Test the pre-allocation methods in the filler module.""" -from itertools import count - import pytest -from execution_testing.base_types import ( - Address, - TestPrivateKey, - TestPrivateKey2, -) +from execution_testing.base_types import Account, Address from execution_testing.forks import Fork, Prague -from execution_testing.test_types import EOA from execution_testing.vm import Op -from ..pre_alloc import ( - CONTRACT_ADDRESS_INCREMENTS_DEFAULT, - CONTRACT_START_ADDRESS_DEFAULT, - Alloc, - AllocMode, -) +from ...shared.pre_alloc import AllocFlags +from ..pre_alloc import Alloc def create_test_alloc( - alloc_mode: AllocMode = AllocMode.PERMISSIVE, + flags: AllocFlags = AllocFlags.MUTABLE, fork: Fork = Prague, ) -> Alloc: """Create a test Alloc instance with default iterators.""" - contract_iter = iter( - Address( - CONTRACT_START_ADDRESS_DEFAULT - + (i * CONTRACT_ADDRESS_INCREMENTS_DEFAULT) - ) - for i in count() - ) - eoa_iter = iter( - EOA( - key=TestPrivateKey + i if i != 1 else TestPrivateKey2, nonce=0 - ).copy() - for i in count() - ) - return Alloc( - alloc_mode=alloc_mode, - contract_address_iterator=contract_iter, - eoa_iterator=eoa_iter, + flags=flags, fork=fork, ) def test_alloc_deploy_contract_basic() -> None: """Test basic `Alloc.deploy_contract` functionality.""" - pre = create_test_alloc() + pre_1 = create_test_alloc() + pre_2 = create_test_alloc() + + contract_code_a = Op.SSTORE(0, 1) + Op.STOP + contract_code_b = Op.SSTORE(0, 2) + Op.STOP - contract_1 = pre.deploy_contract(Op.SSTORE(0, 1) + Op.STOP) - contract_2 = pre.deploy_contract(Op.SSTORE(0, 2) + Op.STOP) + contract_1_a_1 = pre_1.deploy_contract(contract_code_a) + contract_1_a_2 = pre_1.deploy_contract(contract_code_a) + contract_1_b = pre_1.deploy_contract(contract_code_b) # Contracts should be deployed to different addresses - assert contract_1 != contract_2 - assert contract_1 in pre - assert contract_2 in pre - - # Check that addresses follow expected pattern - assert contract_1 == Address(CONTRACT_START_ADDRESS_DEFAULT) - assert contract_2 == Address( - CONTRACT_START_ADDRESS_DEFAULT + CONTRACT_ADDRESS_INCREMENTS_DEFAULT - ) + assert contract_1_a_1 != contract_1_a_2 + assert contract_1_b != contract_1_a_1 + assert contract_1_b != contract_1_a_2 + assert contract_1_a_1 in pre_1 + assert contract_1_a_2 in pre_1 + assert contract_1_a_1 not in pre_2 # Check accounts exist and have code - pre_contract_1_account = pre[contract_1] - pre_contract_2_account = pre[contract_2] - assert pre_contract_1_account is not None - assert pre_contract_2_account is not None - assert pre_contract_1_account.code is not None - assert pre_contract_2_account.code is not None - assert len(pre_contract_1_account.code) > 0 - assert len(pre_contract_2_account.code) > 0 + pre_contract_1_a_1_account = pre_1[contract_1_a_1] + pre_contract_1_a_2_account = pre_1[contract_1_a_2] + assert pre_contract_1_a_1_account is not None + assert pre_contract_1_a_2_account is not None + assert pre_contract_1_a_1_account.code is not None + assert pre_contract_1_a_2_account.code is not None + assert len(pre_contract_1_a_1_account.code) > 0 + assert len(pre_contract_1_a_2_account.code) > 0 + + # Deploy contracts in second pre, verify addresses + contract_2_a_1 = pre_2.deploy_contract(contract_code_a) + contract_2_a_2 = pre_2.deploy_contract(contract_code_a) + contract_2_b = pre_2.deploy_contract(contract_code_b) + + assert contract_1_a_1 == contract_2_a_1 + assert contract_1_a_2 == contract_2_a_2 + assert contract_1_b == contract_2_b def test_alloc_deploy_contract_with_balance() -> None: """Test `Alloc.deploy_contract` with balance.""" pre = create_test_alloc() balance = 10**18 - contract = pre.deploy_contract(Op.STOP, balance=balance) - - assert contract in pre - account = pre[contract] + contract_with_balance_1 = pre.deploy_contract(Op.STOP, balance=balance) + contract_with_balance_2 = pre.deploy_contract(Op.STOP, balance=balance) + contract_without_balance = pre.deploy_contract(Op.STOP, balance=0) + assert contract_with_balance_1 != contract_without_balance + assert contract_with_balance_1 != contract_with_balance_2 + + assert contract_with_balance_1 in pre + account = pre[contract_with_balance_1] assert account is not None assert account.balance == balance + # Redeploy in another pre + pre_2 = create_test_alloc() + assert contract_with_balance_1 == pre_2.deploy_contract( + Op.STOP, balance=balance + ) + assert contract_with_balance_2 == pre_2.deploy_contract( + Op.STOP, balance=balance + ) + def test_alloc_deploy_contract_with_storage() -> None: """Test `Alloc.deploy_contract` with storage.""" pre = create_test_alloc() - storage = {0: 42, 1: 100} - contract = pre.deploy_contract( + storage_a = {0: 42, 1: 100} + contract_with_storage_1 = pre.deploy_contract( Op.STOP, - storage=storage, # type: ignore + storage=storage_a, # type: ignore ) + contract_with_storage_2 = pre.deploy_contract( + Op.STOP, + storage=storage_a, # type: ignore + ) + contract_without_storage = pre.deploy_contract(Op.STOP, storage={}) # type: ignore + assert contract_with_storage_1 != contract_without_storage + assert contract_with_storage_1 != contract_with_storage_2 - assert contract in pre - account = pre[contract] + assert contract_with_storage_1 in pre + account = pre[contract_with_storage_1] assert account is not None assert account.storage is not None - assert account.storage[0] == 42 - assert account.storage[1] == 100 + assert account.storage[0] == storage_a[0] + assert account.storage[1] == storage_a[1] + + # Redeploy in another pre + pre_2 = create_test_alloc() + assert contract_with_storage_1 == pre_2.deploy_contract( + Op.STOP, + storage=storage_a, # type: ignore + ) + assert contract_with_storage_2 == pre_2.deploy_contract( + Op.STOP, + storage=storage_a, # type: ignore + ) def test_alloc_fund_eoa_basic() -> None: @@ -129,7 +144,7 @@ def test_alloc_fund_eoa_basic() -> None: def test_alloc_fund_address() -> None: """Test `Alloc.fund_address` functionality.""" - pre = create_test_alloc() + pre = create_test_alloc(flags=AllocFlags.ALLOW_FUND_ADDRESS) address = Address(0x1234567890123456789012345678901234567890) amount = 5 * 10**18 @@ -140,6 +155,14 @@ def test_alloc_fund_address() -> None: assert account is not None assert account.balance == amount + # Without flag: should raise + pre_without_flag = create_test_alloc() + with pytest.raises( + ValueError, + match="Cannot use pre.fund_address without proper marker", + ): + pre_without_flag.fund_address(address, amount) + def test_alloc_empty_account() -> None: """Test `Alloc.empty_account` functionality.""" @@ -166,42 +189,132 @@ def test_alloc_deploy_contract_code_types() -> None: assert account.code == bytes.fromhex("600160005500") -@pytest.mark.parametrize( - "alloc_mode", [AllocMode.STRICT, AllocMode.PERMISSIVE] -) -def test_alloc_modes(alloc_mode: AllocMode) -> None: +@pytest.mark.parametrize("flags", [AllocFlags.NONE, AllocFlags.MUTABLE]) +def test_alloc_flags(flags: AllocFlags) -> None: """Test different allocation modes.""" - pre = create_test_alloc(alloc_mode=alloc_mode) + pre = create_test_alloc(flags=flags) - assert pre._alloc_mode == alloc_mode + assert pre._flags == flags # Test that we can deploy contracts regardless of mode contract = pre.deploy_contract(Op.STOP) assert contract in pre +def test_alloc_flag_allow_account_address_set() -> None: + """Test ALLOW_ADDRESS_SET_TO_ACCOUNT flag.""" + # With flag: should allow setting accounts directly + pre_with_flag = create_test_alloc( + flags=AllocFlags.ALLOW_ADDRESS_SET_TO_ACCOUNT + ) + address = Address(0x1234567890123456789012345678901234567890) + + pre_with_flag[address] = Account(balance=100) + assert address in pre_with_flag + + # Without flag: should raise + pre_without_flag = create_test_alloc(flags=AllocFlags.NONE) + with pytest.raises( + ValueError, match="Cannot set an account to an address" + ): + pre_without_flag[address] = Account(balance=100) + + +def test_alloc_flag_allow_deploy_to_hardcoded_address() -> None: + """Test ALLOW_DEPLOY_TO_HARDCODED_ADDRESS flag.""" + # With flag: should allow deploying to hardcoded address + pre_with_flag = create_test_alloc( + flags=AllocFlags.ALLOW_DEPLOY_TO_HARDCODED_ADDRESS + ) + hardcoded_address = Address(0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF) + contract = pre_with_flag.deploy_contract( + Op.STOP, address=hardcoded_address + ) + assert contract == hardcoded_address + assert contract in pre_with_flag + + # Without flag: should raise + pre_without_flag = create_test_alloc(flags=AllocFlags.NONE) + with pytest.raises(ValueError, match="Cannot deploy to hardcoded address"): + pre_without_flag.deploy_contract(Op.STOP, address=hardcoded_address) + + +def test_alloc_flag_allow_zero_nonce_contracts() -> None: + """Test ALLOW_ZERO_NONCE_CONTRACTS flag.""" + # With flag: should allow deploying contracts with nonce 0 + pre_with_flag = create_test_alloc( + flags=AllocFlags.ALLOW_ZERO_NONCE_CONTRACTS + ) + contract = pre_with_flag.deploy_contract(Op.STOP, nonce=0) + assert contract in pre_with_flag + account = pre_with_flag[contract] + assert account is not None + assert account.nonce == 0 + + # Without flag: should raise + pre_without_flag = create_test_alloc(flags=AllocFlags.NONE) + with pytest.raises( + ValueError, match="Cannot deploy contracts with zero nonce" + ): + pre_without_flag.deploy_contract(Op.STOP, nonce=0) + + +def test_alloc_flag_allow_fund_address() -> None: + """Test ALLOW_FUND_ADDRESS flag.""" + pre_with_flag = create_test_alloc(flags=AllocFlags.ALLOW_FUND_ADDRESS) + address = Address(0x9999999999999999999999999999999999999999) + amount = 5 * 10**18 + + # Should work for new address (not in pre yet) + pre_with_flag.fund_address(address, amount) + assert address in pre_with_flag + assert pre_with_flag[address].balance == amount + + +def test_alloc_mutable_flag_combines_permissions() -> None: + """Test that MUTABLE flag includes multiple permissions.""" + pre = create_test_alloc(flags=AllocFlags.MUTABLE) + + # Should allow account address set + address = Address(0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA) + + pre[address] = Account(balance=100) + assert address in pre + + # Should allow deploying to hardcoded address + hardcoded_address = Address(0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB) + contract = pre.deploy_contract(Op.STOP, address=hardcoded_address) + assert contract == hardcoded_address + + # Should allow zero nonce contracts + zero_nonce_contract = pre.deploy_contract(Op.STOP, nonce=0) + assert pre[zero_nonce_contract].nonce == 0 + + def test_global_address_allocation_consistency() -> None: """Test that address allocation produces consistent results.""" # Create two alloc instances with same parameters - pre1 = create_test_alloc() - pre2 = create_test_alloc() + pre_1 = create_test_alloc() + pre_2 = create_test_alloc() # Deploy contracts and check they get the same addresses - contract1_pre1 = pre1.deploy_contract(Op.STOP) - contract1_pre2 = pre2.deploy_contract(Op.STOP) + contract_1_pre_1 = pre_1.deploy_contract(Op.STOP) + contract_1_pre_2 = pre_2.deploy_contract(Op.STOP) # Should get same starting address - assert contract1_pre1 == contract1_pre2 - assert contract1_pre1 == Address(CONTRACT_START_ADDRESS_DEFAULT) + assert contract_1_pre_1 == contract_1_pre_2 # Second contracts should also match - contract2_pre1 = pre1.deploy_contract(Op.STOP) - contract2_pre2 = pre2.deploy_contract(Op.STOP) + contract_2_pre_1 = pre_1.deploy_contract(Op.STOP) + contract_2_pre_2 = pre_2.deploy_contract(Op.STOP) - assert contract2_pre1 == contract2_pre2 - assert contract2_pre1 == Address( - CONTRACT_START_ADDRESS_DEFAULT + CONTRACT_ADDRESS_INCREMENTS_DEFAULT - ) + assert contract_2_pre_1 == contract_2_pre_2 + + # Third contract, when distinct, should not match + contract_3_pre_1 = pre_1.deploy_contract(Op.INVALID) + contract_3_pre_2 = pre_2.deploy_contract(Op.STOP) + + assert contract_3_pre_1 != contract_3_pre_2 def test_alloc_deploy_contract_nonce() -> None: @@ -219,7 +332,7 @@ def test_alloc_fund_eoa_returns_eoa_object() -> None: """Test that fund_eoa returns proper EOA object with private key access.""" pre = create_test_alloc() - eoa = pre.fund_eoa(10**18) + eoa = pre.fund_eoa() # Should be able to access private key (EOA object) assert hasattr(eoa, "key") @@ -229,23 +342,4 @@ def test_alloc_fund_eoa_returns_eoa_object() -> None: assert eoa in pre account = pre[eoa] assert account is not None - assert account.balance == 10**18 - - -def test_alloc_multiple_contracts_sequential_addresses() -> None: - """Test that multiple contracts get sequential addresses.""" - pre = create_test_alloc() - - contracts = [] - for i in range(5): - contract = pre.deploy_contract(Op.PUSH1(i) + Op.STOP) - contracts.append(contract) - - # Check addresses are sequential - for i, contract in enumerate(contracts): - expected_addr = Address( - CONTRACT_START_ADDRESS_DEFAULT - + (i * CONTRACT_ADDRESS_INCREMENTS_DEFAULT) - ) - assert contract == expected_addr - assert contract in pre + assert account.balance == pre._eoa_fund_amount_default diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/execute_fill.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/execute_fill.py index b0594208381..88fe039d8da 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/execute_fill.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/execute_fill.py @@ -2,6 +2,7 @@ Shared pytest fixtures and hooks for EEST generation modes (fill and execute). """ +from dataclasses import dataclass from typing import List import pytest @@ -15,6 +16,7 @@ from execution_testing.specs.base import OpMode from execution_testing.test_types import EOA, Alloc, ChainConfig +from ..shared.pre_alloc import AllocFlags from ..spec_version_checker.spec_version_checker import EIPSpecTestItem ALL_FIXTURE_PARAMETERS = { @@ -33,6 +35,62 @@ """ +@dataclass +class AllocFlagsMarker: + """Marker for allocation flags.""" + + name: str + description: str + flag: AllocFlags + + +ALLOC_FLAGS_MARKERS = [ + AllocFlagsMarker( + name="pre_alloc_mutable", + description=( + "Marks a test to allow impossible mutations in the pre-state." + ), + flag=AllocFlags.MUTABLE, + ), + AllocFlagsMarker( + name="pre_fund_address", + description="Marks a test to so it can use the `pre.fund_eoa` method.", + flag=AllocFlags.ALLOW_FUND_ADDRESS, + ), + AllocFlagsMarker( + name="pre_eoa_with_code", + description="Marks a test to allow creating EOAs with code.", + flag=AllocFlags.ALLOW_EOA_WITH_CODE, + ), + AllocFlagsMarker( + name="pre_eoa_with_hardcoded_nonce", + description=( + "Marks a test to allow creating EOAs with a hardcoded nonce." + ), + flag=AllocFlags.ALLOW_EOA_WITH_HARDCODED_NONCE, + ), + AllocFlagsMarker( + name="pre_zero_nonce_contracts", + description=( + "Marks a test to allow deploying contracts with zero nonce." + ), + flag=AllocFlags.ALLOW_ZERO_NONCE_CONTRACTS, + ), + AllocFlagsMarker( + name="pre_deploy_to_hardcoded_address", + description=( + "Marks a test to allow deploying to a hardcoded address." + ), + flag=AllocFlags.ALLOW_DEPLOY_TO_HARDCODED_ADDRESS, + ), + AllocFlagsMarker( + name="pre_address_set_to_account", + description="Marks a test to allow setting an address to an account.", + flag=AllocFlags.ALLOW_ADDRESS_SET_TO_ACCOUNT, + ), +] + + @pytest.hookimpl(tryfirst=True) def pytest_configure(config: pytest.Config) -> None: """ @@ -157,11 +215,6 @@ def pytest_configure(config: pytest.Config) -> None: "pre_alloc_group: Control shared pre-allocation grouping (use " '"separate" for isolated group or custom string for named groups)', ) - config.addinivalue_line( - "markers", - "pre_alloc_modify: Marks a test to apply plugin-specific " - "pre_alloc_group modifiers", - ) config.addinivalue_line( "markers", "slow: Marks a test as slow (deselect with '-m \"not slow\"')", @@ -183,6 +236,11 @@ def pytest_configure(config: pytest.Config) -> None: "markers", "fully_tagged: Marks a static test as fully tagged with all metadata.", ) + for flags in ALLOC_FLAGS_MARKERS: + config.addinivalue_line( + "markers", + f"{flags.name}: {flags.description}", + ) @pytest.fixture(scope="function") @@ -270,6 +328,31 @@ def chain_config() -> ChainConfig: return ChainConfig() +@pytest.fixture(scope="function") +def alloc_flags_from_test_markers( + request: pytest.FixtureRequest, +) -> AllocFlags: + """Return allocation mode for a given test based on its markers.""" + flags = AllocFlags.NONE + for flag in ALLOC_FLAGS_MARKERS: + if request.node.get_closest_marker(flag.name): + flags |= flag.flag + return flags + + +@pytest.fixture(scope="function") +def alloc_flags( + alloc_flags_from_test_markers: AllocFlags, +) -> AllocFlags: + """ + Return allocation mode for the test. + + By default, this is based on markers tests only, but plugins can + override this behavior. + """ + return alloc_flags_from_test_markers + + def pytest_addoption(parser: pytest.Parser) -> None: """Add command-line options to pytest.""" static_filler_group = parser.getgroup( diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/pre_alloc.py new file mode 100644 index 00000000000..d19e2706987 --- /dev/null +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/pre_alloc.py @@ -0,0 +1,398 @@ +"""Shared pre-alloc functionality.""" + +from enum import IntFlag, auto +from typing import Any, Literal + +from pydantic import PrivateAttr + +from execution_testing.base_types import ( + Account, + Address, + Hash, + Number, + Storage, + StorageRootType, +) +from execution_testing.base_types.conversions import ( + BytesConvertible, + FixedSizeBytesConvertible, + NumberConvertible, +) +from execution_testing.forks import Fork +from execution_testing.test_types import EOA +from execution_testing.test_types import Alloc as BaseAlloc + + +class AllocFlags(IntFlag): + """Feature flags for allocation behavior.""" + + NONE = 0 + ALLOW_ADDRESS_SET_TO_ACCOUNT = auto() + ALLOW_DEPLOY_TO_HARDCODED_ADDRESS = auto() + ALLOW_ZERO_NONCE_CONTRACTS = auto() + ALLOW_EOA_WITH_CODE = auto() + ALLOW_EOA_WITH_HARDCODED_NONCE = auto() + ALLOW_FUND_ADDRESS = auto() + + MUTABLE = ( + ALLOW_ADDRESS_SET_TO_ACCOUNT + | ALLOW_DEPLOY_TO_HARDCODED_ADDRESS + | ALLOW_ZERO_NONCE_CONTRACTS + | ALLOW_EOA_WITH_CODE + | ALLOW_EOA_WITH_HARDCODED_NONCE + ) + + def is_mutable(self) -> bool: + """Return whether the pre-alloc is mutable.""" + return bool(self & AllocFlags.MUTABLE) + + def assert_allow_account_address_set(self) -> None: + """ + Raise an exception if the ALLOW_ADDRESS_SET_TO_ACCOUNT flag is not set. + """ + if AllocFlags.ALLOW_ADDRESS_SET_TO_ACCOUNT not in self: + raise ValueError( + "Cannot set an account to an address (pre[a] = b) without " + "proper marker. " + "Use `pytest.mark.pre_address_set_to_account` to allow this." + ) + return + + def assert_allow_deploy_to_hardcoded_address(self) -> None: + """ + Raise an exception if the ALLOW_DEPLOY_TO_HARDCODED_ADDRESS flag is + not set. + """ + if AllocFlags.ALLOW_DEPLOY_TO_HARDCODED_ADDRESS not in self: + raise ValueError( + "Cannot deploy to hardcoded address without proper marker. " + "Use `pytest.mark.pre_deploy_to_hardcoded_address` to allow " + "this." + ) + return + + def assert_allow_zero_nonce_contracts(self) -> None: + """ + Raise an exception if the ALLOW_ZERO_NONCE_CONTRACTS flag is not set. + """ + if AllocFlags.ALLOW_ZERO_NONCE_CONTRACTS not in self: + raise ValueError( + "Cannot deploy contracts with zero nonce without proper " + "marker. " + "Use `pytest.mark.pre_zero_nonce_contracts` to allow this." + ) + return + + def assert_allow_fund_address(self) -> None: + """Raise an exception if the ALLOW_FUND_ADDRESS flag is not set.""" + if AllocFlags.ALLOW_FUND_ADDRESS not in self: + raise ValueError( + "Cannot use pre.fund_address without proper marker. " + "Use `pytest.mark.pre_fund_address` to allow this." + ) + return + + def assert_allow_eoa_with_code(self) -> None: + """Raise an exception if the ALLOW_EOA_WITH_CODE flag is not set.""" + if AllocFlags.ALLOW_EOA_WITH_CODE not in self: + raise ValueError( + "Cannot create EOAs with code without proper marker. " + "Use `pytest.mark.pre_eoa_with_code` to allow this." + ) + return + + def assert_allow_eoa_with_hardcoded_nonce(self) -> None: + """ + Raise an exception if the ALLOW_EOA_WITH_HARDCODED_NONCE flag is not + set. + """ + if AllocFlags.ALLOW_EOA_WITH_HARDCODED_NONCE not in self: + raise ValueError( + "Cannot create EOAs with a hardcoded nonce without proper " + "marker. " + "Use `pytest.mark.pre_eoa_with_hardcoded_nonce` to allow this." + ) + return + + def assert_mutable(self) -> None: + """Raises an exception if the MUTABLE flag is not set.""" + if self.is_mutable(): + raise ValueError( + "Cannot set items in immutable mode. " + "Use `pytest.mark.pre_alloc_mutable` to allow mutable mode." + ) + return + + +class Alloc(BaseAlloc): + """ + Allocation subclass that enforces rules set by the allocation flags. + """ + + _fork: Fork = PrivateAttr() + _flags: AllocFlags = PrivateAttr(AllocFlags.NONE) + + def __init__( + self, + *args: Any, + fork: Fork, + flags: AllocFlags, + **kwargs: Any, + ) -> None: + """Initialize allocation with the given properties.""" + super().__init__(*args, **kwargs) + self._fork = fork + self._flags = flags + + def __setitem__( + self, + address: Address | FixedSizeBytesConvertible, + account: Account | None, + ) -> None: + """Set account associated with an address.""" + self._flags.assert_allow_account_address_set() + self.__internal_setitem__(address, account) + + def __internal_setitem__( + self, + address: Address | FixedSizeBytesConvertible, + account: Account | None, + ) -> None: + """ + Set account associated with an address. + + Called by the pre-alloc implementation to set an account. + """ + if not isinstance(address, Address): + address = Address(address) + self.root[address] = account + + def __delitem__( + self, address: Address | FixedSizeBytesConvertible + ) -> None: + """Delete account associated with an address.""" + self._flags.assert_allow_account_address_set() + self.__internal_delitem__(address) + + def __internal_delitem__( + self, + address: Address | FixedSizeBytesConvertible, + ) -> None: + """ + Delete account associated with an address. + + Called by the pre-alloc implementation to delete an account. + """ + if not isinstance(address, Address): + address = Address(address) + self.root.pop(address, None) + + def deterministic_deploy_contract( + self, + *, + deploy_code: BytesConvertible, + salt: Hash | int = 0, + initcode: BytesConvertible | None = None, + storage: Storage | StorageRootType | None = None, + label: str | None = None, + ) -> Address: + """ + Deploy a contract to the allocation at a deterministic location + using a deterministic deployment proxy. + + The initcode is not executed during test filling; it is executed only + when the tests run on live networks. Therefore, if the initcode + performs modifications to the storage, these must be specified using + the `storage` parameter. + + Args: + deploy_code: Contract code to deploy. + salt: Salt to use for deterministic deployment. + initcode: Initcode to use for deterministic deployment. + If `None`, the initcode is derived from `deploy_code`. + storage: The expected storage state of the deployed contract after + initcode execution. + label: Label to use for the contract. + + """ + return self._deterministic_deploy_contract( + deploy_code=deploy_code, + salt=salt, + initcode=initcode, + storage=storage, + label=label, + ) + + def _deterministic_deploy_contract( + self, + *, + deploy_code: BytesConvertible, + salt: Hash | int, + initcode: BytesConvertible | None, + storage: Storage | StorageRootType | None, + label: str | None, + ) -> Address: + """ + Sub-class implementation of deterministic contract deployment. + """ + raise NotImplementedError( + "_deterministic_deploy_contract is not implemented in the base " + "class" + ) + + def deploy_contract( + self, + code: BytesConvertible, + *, + storage: Storage | StorageRootType | None = None, + balance: NumberConvertible = 0, + nonce: NumberConvertible = 1, + address: Address | None = None, + label: str | None = None, + stub: 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: + self._flags.assert_allow_deploy_to_hardcoded_address() + + if Number(nonce) == 0: + self._flags.assert_allow_zero_nonce_contracts() + + return self._deploy_contract( + code=code, + storage=storage, + balance=balance, + nonce=nonce, + address=address, + label=label, + stub=stub, + ) + + def _deploy_contract( + self, + code: BytesConvertible, + *, + storage: Storage | StorageRootType | None, + balance: NumberConvertible, + nonce: NumberConvertible, + address: Address | None, + label: str | None, + stub: str | None, + ) -> Address: + """ + Sub-class implementation of deploy_contract. + """ + raise NotImplementedError( + "_deploy_contract is not implemented in the base class" + ) + + def fund_eoa( + self, + amount: NumberConvertible | None = None, + label: str | None = None, + storage: Storage | None = None, + code: BytesConvertible | None = None, + delegation: Address | Literal["Self"] | None = None, + nonce: NumberConvertible | None = None, + ) -> EOA: + """ + Add a previously unused EOA to the pre-alloc with the balance specified + by `amount`. + + If amount is 0, nothing will be added to the pre-alloc but a new and + unique EOA will be returned. + """ + if code is not None: + self._flags.assert_allow_eoa_with_code() + + if nonce is not None: + self._flags.assert_allow_eoa_with_hardcoded_nonce() + + return self._fund_eoa( + amount=amount, + label=label, + storage=storage, + code=code, + delegation=delegation, + nonce=nonce, + ) + + def _fund_eoa( + self, + amount: NumberConvertible | None, + label: str | None, + storage: Storage | None, + code: BytesConvertible | None, + delegation: Address | Literal["Self"] | None, + nonce: NumberConvertible | None, + ) -> EOA: + """ + Sub-class implementation of fund_eoa. + """ + raise NotImplementedError( + "_fund_eoa is not implemented in the base class" + ) + + def fund_address( + self, + address: Address, + amount: NumberConvertible, + *, + minimum_balance: bool = False, + ) -> None: + """ + 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. + + Args: + address: Address to fund + amount: Amount to fund in Wei + minimum_balance: If set to True, account will be checked to have a + minimum balance of `amount` and only fund if the balance is + insufficient + + """ + self._flags.assert_allow_fund_address() + return self._fund_address( + address=address, + amount=amount, + minimum_balance=minimum_balance, + ) + + def _fund_address( + self, + address: Address, + amount: NumberConvertible, + minimum_balance: bool, + ) -> None: + """ + Sub-class implementation of fund_address. + """ + raise NotImplementedError( + "_fund_address is not implemented in the base class" + ) + + def empty_account(self) -> Address: + """ + Return a previously unused account guaranteed to be empty. + + This ensures the account has zero balance, zero nonce, no code, and no + storage. The account is not a precompile or a system contract. + """ + return self._empty_account() + + def _empty_account(self) -> Address: + """ + Sub-class implementation of empty_account. + """ + raise NotImplementedError( + "_empty_account is not implemented in the base class" + ) diff --git a/packages/testing/src/execution_testing/specs/static_state/state_static.py b/packages/testing/src/execution_testing/specs/static_state/state_static.py index 91f7217811f..cdfd2974531 100644 --- a/packages/testing/src/execution_testing/specs/static_state/state_static.py +++ b/packages/testing/src/execution_testing/specs/static_state/state_static.py @@ -227,7 +227,7 @@ def test_state_vectors( "separate", reason="Uses hard-coded addresses" )(test_state_vectors) if not fully_tagged: - test_state_vectors = pytest.mark.pre_alloc_modify( + test_state_vectors = pytest.mark.pre_alloc_mutable( test_state_vectors ) From d90b71c50300581140a97d66d78ce537b558f8c5 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Sat, 7 Feb 2026 01:32:07 +0000 Subject: [PATCH 06/22] Fixes --- docs/writing_tests/test_markers.md | 26 ++++- .../plugins/execute/pre_alloc.py | 4 +- .../pytest_commands/plugins/filler/filler.py | 94 ++++++++++++++++++- .../plugins/filler/pre_alloc.py | 9 +- .../plugins/filler/tests/test_pre_alloc.py | 11 ++- .../filler/tests/test_prealloc_group.py | 51 +++++++--- .../plugins/shared/pre_alloc.py | 11 +++ .../src/execution_testing/specs/base.py | 58 ------------ 8 files changed, 180 insertions(+), 84 deletions(-) diff --git a/docs/writing_tests/test_markers.md b/docs/writing_tests/test_markers.md index 73cf74cf451..d25566b5f23 100644 --- a/docs/writing_tests/test_markers.md +++ b/docs/writing_tests/test_markers.md @@ -327,7 +327,7 @@ In this example, the test will be marked as expected to fail when it is being ex This marker is used to mark tests that are slow to run. These tests are not run during [`tox` checks](./verifying_changes.md), and are only run when a release is being prepared. -### `@pytest.mark.pre_alloc_modify` +### `@pytest.mark.pre_alloc_mutable` This marker is used to mark tests that modify the pre-alloc in a way that would be impractical to reproduce in a real-world scenario. @@ -336,6 +336,30 @@ Examples of this include: - Modifying the pre-alloc to have a balance of 2^256 - 1. - Address collisions that would require hash collisions. +### `@pytest.mark.pre_fund_address` + +This marker is used to mark tests that use the `pre.fund_eoa()` method to create and fund EOAs dynamically. + +### `@pytest.mark.pre_eoa_with_code` + +This marker is used to mark tests that create EOAs with code, which is not possible in a real-world scenario but may be useful for testing edge cases. + +### `@pytest.mark.pre_eoa_with_hardcoded_nonce` + +This marker is used to mark tests that create EOAs with a hardcoded nonce value, rather than the default nonce of 0. + +### `@pytest.mark.pre_zero_nonce_contracts` + +This marker is used to mark tests that deploy contracts with a nonce of zero, which is not possible in a real-world scenario. + +### `@pytest.mark.pre_deploy_to_hardcoded_address` + +This marker is used to mark tests that deploy contracts to a hardcoded address, rather than using the standard address derivation from the sender and nonce. + +### `@pytest.mark.pre_address_set_to_account` + +This marker is used to mark tests that set an address to an account directly in the pre-allocation. + ### `@pytest.mark.skip()` This marker can be used to skip a test. diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py index 884e595b4e8..e07c6e96935 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py @@ -41,7 +41,7 @@ from execution_testing.tools import Initcode from execution_testing.vm import Bytecode, Op -from ..shared.pre_alloc import Alloc as BaseAlloc +from ..shared.pre_alloc import Alloc as SharedAlloc from ..shared.pre_alloc import AllocFlags from .contracts import ( check_deterministic_factory_deployment, @@ -225,7 +225,7 @@ class PendingTransaction(Transaction): value: HexNumber | None = None # type: ignore -class Alloc(BaseAlloc): +class Alloc(SharedAlloc): """A custom class that inherits from the original Alloc class.""" _sender: EOA = PrivateAttr() diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py index 241d651c2e9..4c2e26e0ef2 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py @@ -10,6 +10,7 @@ import configparser import datetime import gc +import hashlib import json import logging import os @@ -77,6 +78,7 @@ is_help_or_collectonly_mode, labeled_format_parameter_set, ) +from ..shared.pre_alloc import AllocFlags from ..spec_version_checker.spec_version_checker import ( get_ref_spec_from_module, ) @@ -1395,6 +1397,83 @@ def fixture_source_url( return github_url +def compute_pre_alloc_group_hash( + *, + base_test: BaseTest, + alloc_flags: AllocFlags, +) -> str: + """Hash (fork, env) in order to group tests by genesis config.""" + if not hasattr(base_test, "pre"): + raise AttributeError( + f"{base_test.__class__.__name__} does not have a 'pre' field. " + "Pre-allocation group usage is only supported for test " + "types that define pre-allocs." + ) + fork_digest = hashlib.sha256( + base_test.fork.name().encode("utf-8") + ).digest() + fork_hash = int.from_bytes(fork_digest[:8], byteorder="big") + genesis_env = base_test.get_genesis_environment() + combined_hash = fork_hash ^ hash(genesis_env) + + # Check if test has pre_alloc_group marker + if base_test._request is not None and hasattr(base_test._request, "node"): + pre_alloc_group_marker = base_test._request.node.get_closest_marker( + "pre_alloc_group" + ) + group_salt: str = "" + if pre_alloc_group_marker: + # Get the group name/salt from marker args + if pre_alloc_group_marker.args: + group_salt = str(pre_alloc_group_marker.args[0]) + else: + group_salt = "separate" + else: + if not alloc_flags.incompatible_with_alloc_grouping(): + group_salt = "separate" + + if group_salt: + if group_salt == "separate": + # Use nodeid for unique group per test + group_salt = base_test._request.node.nodeid + # Add custom salt to hash + salt_hash = hashlib.sha256(group_salt.encode("utf-8")).digest() + salt_int = int.from_bytes(salt_hash[:8], byteorder="big") + combined_hash = combined_hash ^ salt_int + + return f"0x{combined_hash:016x}" + + +def update_pre_alloc_groups( + *, + base_test: BaseTest, + pre_alloc_group_builders: PreAllocGroupBuilders, + test_id: str, + alloc_flags: AllocFlags = AllocFlags.NONE, +) -> None: + """ + Create or update the pre-allocation group with the pre from the current + spec. + """ + if not hasattr(base_test, "pre"): + raise AttributeError( + f"{base_test.__class__.__name__} does not have a 'pre' field. " + "Pre-allocation groups are only supported for test types " + "that define pre-allocation." + ) + pre_alloc_hash = compute_pre_alloc_group_hash( + base_test=base_test, + alloc_flags=alloc_flags, + ) + pre_alloc_group_builders.add_test_pre( + pre_alloc_hash=pre_alloc_hash, + test_id=str(test_id), + fork=base_test.fork, + environment=base_test.get_genesis_environment(), + pre=base_test.pre, + ) + + def base_test_parametrizer(cls: Type[BaseTest]) -> Any: """ Generate pytest.fixture for a given BaseTest subclass. @@ -1424,6 +1503,7 @@ def base_test_parametrizer_func( gas_benchmark_value: int, fixed_opcode_count: int | None, witness_generator: Any, + alloc_flags: AllocFlags, ) -> Any: """ Fixture used to instantiate an auto-fillable BaseTest object from @@ -1478,13 +1558,18 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: # Get the filling session from config session: FillingSession = request.config.filling_session # type: ignore + assert isinstance(session, FillingSession) # Phase 1: Generate pre-allocation groups if session.phase_manager.is_pre_alloc_generation: # Use the original update_pre_alloc_groups method which # returns the groups - self.update_pre_alloc_groups( - session.pre_alloc_group_builders, request.node.nodeid + assert session.pre_alloc_group_builders is not None + update_pre_alloc_groups( + base_test=self, + pre_alloc_group_builders=session.pre_alloc_group_builders, + test_id=request.node.nodeid, + alloc_flags=alloc_flags, ) return # Skip fixture generation in phase 1 @@ -1495,7 +1580,10 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: FixtureFillingPhase.PRE_ALLOC_GENERATION in fixture_format.format_phases ): - pre_alloc_hash = self.compute_pre_alloc_group_hash() + pre_alloc_hash = compute_pre_alloc_group_hash( + base_test=self, + alloc_flags=alloc_flags, + ) group = session.get_pre_alloc_group(pre_alloc_hash) self.pre = group.pre try: diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py index 26654c3b99d..50bdcc74d79 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py @@ -34,7 +34,7 @@ ) from execution_testing.tools import Initcode -from ..shared.pre_alloc import Alloc as BaseAlloc +from ..shared.pre_alloc import Alloc as SharedAlloc from ..shared.pre_alloc import AllocFlags @@ -77,7 +77,7 @@ def eoa_from_account(account_hash: Hash, salt: int) -> EOA: return EOA(key=Bytes(account_hash + salt.to_bytes(64, "big")).sha256()) -class Alloc(BaseAlloc): +class Alloc(SharedAlloc): """Allocation of accounts in the state, pre and post test execution.""" _eoa_fund_amount_default: int = PrivateAttr(10**21) @@ -263,7 +263,7 @@ def _fund_eoa( raise Exception( "code and delegation cannot be set at the same time" ) - if storage is None and delegation is None: + if storage is None and delegation is None and code is None: nonce = Number(0 if nonce is None else nonce) account = Account( nonce=nonce, @@ -272,7 +272,6 @@ def _fund_eoa( else: # Type-4 transaction is sent to the EOA to set the storage, so # the nonce must be 1 - code = b"" if delegation is not None: if ( not isinstance(delegation, Address) @@ -285,6 +284,8 @@ def _fund_eoa( code = DELEGATION_DESIGNATION + delegation elif code is not None: code = Bytes(code) + else: + code = b"" # If delegation is None but storage is not, realistically the # nonce should be 2 because the account must have delegated to # set the storage and then again to reset the delegation (but diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_pre_alloc.py index 1c795f44134..fe1bec74fa1 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_pre_alloc.py @@ -98,7 +98,7 @@ def test_alloc_deploy_contract_with_storage() -> None: Op.STOP, storage=storage_a, # type: ignore ) - contract_without_storage = pre.deploy_contract(Op.STOP, storage={}) # type: ignore + contract_without_storage = pre.deploy_contract(Op.STOP, storage={}) assert contract_with_storage_1 != contract_without_storage assert contract_with_storage_1 != contract_with_storage_2 @@ -268,7 +268,9 @@ def test_alloc_flag_allow_fund_address() -> None: # Should work for new address (not in pre yet) pre_with_flag.fund_address(address, amount) assert address in pre_with_flag - assert pre_with_flag[address].balance == amount + account = pre_with_flag[address] + assert account is not None + assert account.balance == amount def test_alloc_mutable_flag_combines_permissions() -> None: @@ -288,7 +290,10 @@ def test_alloc_mutable_flag_combines_permissions() -> None: # Should allow zero nonce contracts zero_nonce_contract = pre.deploy_contract(Op.STOP, nonce=0) - assert pre[zero_nonce_contract].nonce == 0 + assert zero_nonce_contract in pre + account = pre[zero_nonce_contract] + assert account is not None + assert account.nonce == 0 def test_global_address_allocation_consistency() -> None: diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_prealloc_group.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_prealloc_group.py index 650f8fb2a28..744d0a3f4c7 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_prealloc_group.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_prealloc_group.py @@ -12,7 +12,8 @@ from execution_testing.specs.base import BaseTest from execution_testing.test_types import Alloc, Environment -from ..filler import default_output_directory +from ...shared.pre_alloc import AllocFlags +from ..filler import compute_pre_alloc_group_hash, default_output_directory class MockTest(BaseTest): @@ -54,7 +55,9 @@ def test_pre_alloc_group_separate() -> None: # Create test without marker test1 = MockTest(pre=pre, genesis_environment=env, fork=fork) - hash1 = test1.compute_pre_alloc_group_hash() + hash1 = compute_pre_alloc_group_hash( + base_test=test1, alloc_flags=AllocFlags.NONE + ) # Create test with "separate" marker mock_request = Mock() @@ -67,14 +70,18 @@ def test_pre_alloc_group_separate() -> None: test2 = MockTest( pre=pre, genesis_environment=env, request=mock_request, fork=fork ) - hash2 = test2.compute_pre_alloc_group_hash() + hash2 = compute_pre_alloc_group_hash( + base_test=test2, alloc_flags=AllocFlags.NONE + ) # Hashes should be different due to "separate" marker assert hash1 != hash2 # Create another test without marker - should match first test test3 = MockTest(pre=pre, genesis_environment=env, fork=fork) - hash3 = test3.compute_pre_alloc_group_hash() + hash3 = compute_pre_alloc_group_hash( + base_test=test3, alloc_flags=AllocFlags.NONE + ) assert hash1 == hash3 @@ -96,7 +103,9 @@ def test_pre_alloc_group_custom_salt() -> None: test1 = MockTest( pre=pre, genesis_environment=env, request=mock_request1, fork=fork ) - hash1 = test1.compute_pre_alloc_group_hash() + hash1 = compute_pre_alloc_group_hash( + base_test=test1, alloc_flags=AllocFlags.NONE + ) # Create another test with same custom group "eip1234" mock_request2 = Mock() @@ -111,7 +120,9 @@ def test_pre_alloc_group_custom_salt() -> None: test2 = MockTest( pre=pre, genesis_environment=env, request=mock_request2, fork=fork ) - hash2 = test2.compute_pre_alloc_group_hash() + hash2 = compute_pre_alloc_group_hash( + base_test=test2, alloc_flags=AllocFlags.NONE + ) # Hashes should be the same - both in "eip1234" group assert hash1 == hash2 @@ -127,7 +138,9 @@ def test_pre_alloc_group_custom_salt() -> None: test3 = MockTest( pre=pre, genesis_environment=env, request=mock_request3, fork=fork ) - hash3 = test3.compute_pre_alloc_group_hash() + hash3 = compute_pre_alloc_group_hash( + base_test=test3, alloc_flags=AllocFlags.NONE + ) # Hash should be different - different custom group assert hash1 != hash3 @@ -151,7 +164,9 @@ def test_pre_alloc_group_separate_different_nodeids() -> None: test1 = MockTest( pre=pre, genesis_environment=env, request=mock_request1, fork=fork ) - hash1 = test1.compute_pre_alloc_group_hash() + hash1 = compute_pre_alloc_group_hash( + base_test=test1, alloc_flags=AllocFlags.NONE + ) # Create test with "separate" and nodeid2 mock_request2 = Mock() @@ -164,7 +179,9 @@ def test_pre_alloc_group_separate_different_nodeids() -> None: test2 = MockTest( pre=pre, genesis_environment=env, request=mock_request2, fork=fork ) - hash2 = test2.compute_pre_alloc_group_hash() + hash2 = compute_pre_alloc_group_hash( + base_test=test2, alloc_flags=AllocFlags.NONE + ) # Hashes should be different due to different nodeids assert hash1 != hash2 @@ -185,11 +202,15 @@ def test_no_pre_alloc_group_marker() -> None: test1 = MockTest( pre=pre, genesis_environment=env, request=mock_request, fork=fork ) - hash1 = test1.compute_pre_alloc_group_hash() + hash1 = compute_pre_alloc_group_hash( + base_test=test1, alloc_flags=AllocFlags.NONE + ) # Create test without any request test2 = MockTest(pre=pre, genesis_environment=env, fork=fork) - hash2 = test2.compute_pre_alloc_group_hash() + hash2 = compute_pre_alloc_group_hash( + base_test=test2, alloc_flags=AllocFlags.NONE + ) # Hashes should be the same - both have no marker assert hash1 == hash2 @@ -215,7 +236,9 @@ def test_pre_alloc_group_with_reason() -> None: test1 = MockTest( pre=pre, genesis_environment=env, request=mock_request1, fork=fork ) - hash1 = test1.compute_pre_alloc_group_hash() + hash1 = compute_pre_alloc_group_hash( + base_test=test1, alloc_flags=AllocFlags.NONE + ) # Create another test with same group but different reason mock_request2 = Mock() @@ -229,7 +252,9 @@ def test_pre_alloc_group_with_reason() -> None: test2 = MockTest( pre=pre, genesis_environment=env, request=mock_request2, fork=fork ) - hash2 = test2.compute_pre_alloc_group_hash() + hash2 = compute_pre_alloc_group_hash( + base_test=test2, alloc_flags=AllocFlags.NONE + ) # Hashes should be the same - reason doesn't affect grouping assert hash1 == hash2 diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/pre_alloc.py index d19e2706987..f36867b5836 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/pre_alloc.py @@ -46,6 +46,16 @@ def is_mutable(self) -> bool: """Return whether the pre-alloc is mutable.""" return bool(self & AllocFlags.MUTABLE) + def incompatible_with_alloc_grouping(self) -> bool: + """Return True if the restrictions allow pre-alloc grouping.""" + if ( + AllocFlags.ALLOW_ADDRESS_SET_TO_ACCOUNT in self + or AllocFlags.ALLOW_DEPLOY_TO_HARDCODED_ADDRESS in self + or AllocFlags.ALLOW_FUND_ADDRESS in self + ): + return True + return False + def assert_allow_account_address_set(self) -> None: """ Raise an exception if the ALLOW_ADDRESS_SET_TO_ACCOUNT flag is not set. @@ -371,6 +381,7 @@ def _fund_address( self, address: Address, amount: NumberConvertible, + *, minimum_balance: bool, ) -> None: """ diff --git a/packages/testing/src/execution_testing/specs/base.py b/packages/testing/src/execution_testing/specs/base.py index fc8ce0d9f72..b7157a2f8a5 100644 --- a/packages/testing/src/execution_testing/specs/base.py +++ b/packages/testing/src/execution_testing/specs/base.py @@ -2,7 +2,6 @@ Base test class and helper functions for Ethereum state and blockchain tests. """ -import hashlib from abc import abstractmethod from enum import StrEnum, unique from functools import reduce @@ -35,7 +34,6 @@ BaseFixture, FixtureFormat, LabeledFixtureFormat, - PreAllocGroupBuilders, ) from execution_testing.forks import Fork from execution_testing.forks.base_fork import BaseFork @@ -301,61 +299,5 @@ def get_genesis_environment(self) -> Environment: "access for use with pre-allocation groups." ) - def update_pre_alloc_groups( - self, pre_alloc_group_builders: PreAllocGroupBuilders, test_id: str - ) -> None: - """ - Create or update the pre-allocation group with the pre from the current - spec. - """ - if not hasattr(self, "pre"): - raise AttributeError( - f"{self.__class__.__name__} does not have a 'pre' field. " - "Pre-allocation groups are only supported for test types " - "that define pre-allocation." - ) - pre_alloc_hash = self.compute_pre_alloc_group_hash() - pre_alloc_group_builders.add_test_pre( - pre_alloc_hash=pre_alloc_hash, - test_id=str(test_id), - fork=self.fork, - environment=self.get_genesis_environment(), - pre=self.pre, - ) - - def compute_pre_alloc_group_hash(self) -> str: - """Hash (fork, env) in order to group tests by genesis config.""" - if not hasattr(self, "pre"): - raise AttributeError( - f"{self.__class__.__name__} does not have a 'pre' field. " - "Pre-allocation group usage is only supported for test " - "types that define pre-allocs." - ) - fork_digest = hashlib.sha256(self.fork.name().encode("utf-8")).digest() - fork_hash = int.from_bytes(fork_digest[:8], byteorder="big") - genesis_env = self.get_genesis_environment() - combined_hash = fork_hash ^ hash(genesis_env) - - # Check if test has pre_alloc_group marker - if self._request is not None and hasattr(self._request, "node"): - pre_alloc_group_marker = self._request.node.get_closest_marker( - "pre_alloc_group" - ) - if pre_alloc_group_marker: - # Get the group name/salt from marker args - if pre_alloc_group_marker.args: - group_salt = str(pre_alloc_group_marker.args[0]) - if group_salt == "separate": - # Use nodeid for unique group per test - group_salt = self._request.node.nodeid - # Add custom salt to hash - salt_hash = hashlib.sha256( - group_salt.encode("utf-8") - ).digest() - salt_int = int.from_bytes(salt_hash[:8], byteorder="big") - combined_hash = combined_hash ^ salt_int - - return f"0x{combined_hash:016x}" - TestSpec = Callable[[Fork], Generator[BaseTest, None, None]] From 3ed680104ca132e771d4cb3d2d8c8835181ee714 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Sat, 7 Feb 2026 01:32:30 +0000 Subject: [PATCH 07/22] Add correct markers to all tests --- .../test_block_access_lists.py | 6 +- .../test_block_access_lists_opcodes.py | 1 + .../test_beacon_root_contract.py | 19 +----- ..._dynamic_create2_selfdestruct_collision.py | 8 +-- .../eip6780_selfdestruct/test_selfdestruct.py | 57 +++++++++++----- tests/frontier/validation/test_transaction.py | 1 + .../test_initcollision.py | 2 +- .../test_revert_in_create.py | 2 +- .../test_modified_contract.py | 67 ++----------------- .../test_modified_withdrawal_contract.py | 11 ++- .../test_modified_consolidation_contract.py | 11 ++- tests/prague/eip7702_set_code_tx/test_gas.py | 36 ++-------- .../eip7702_set_code_tx/test_set_code_txs.py | 23 ++----- .../test_eip150_selfdestruct.py | 24 ++----- 14 files changed, 81 insertions(+), 187 deletions(-) diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py index 9f7400b433f..a54aa9c8655 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py @@ -2198,10 +2198,7 @@ def test_bal_cross_tx_storage_revert_to_zero( ) -@pytest.mark.pre_alloc_group( - "ripemd160_state_leak", - reason="Pre-funds RIPEMD-160, must be isolated in EngineX format", -) +@pytest.mark.pre_fund_address() def test_bal_cross_block_ripemd160_state_leak( pre: Alloc, blockchain_test: BlockchainTestFiller, @@ -2550,6 +2547,7 @@ def test_bal_all_transaction_types( ) +@pytest.mark.pre_address_set_to_account() def test_bal_lexicographic_address_ordering( pre: Alloc, blockchain_test: BlockchainTestFiller, diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py index c0e8886d350..39537f96754 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py @@ -2434,6 +2434,7 @@ def test_bal_create_selfdestruct_to_self_with_call( ) +@pytest.mark.pre_address_set_to_account() def test_bal_create2_collision( pre: Alloc, blockchain_test: BlockchainTestFiller, diff --git a/tests/cancun/eip4788_beacon_root/test_beacon_root_contract.py b/tests/cancun/eip4788_beacon_root/test_beacon_root_contract.py index 62fb48a364e..c142e049941 100644 --- a/tests/cancun/eip4788_beacon_root/test_beacon_root_contract.py +++ b/tests/cancun/eip4788_beacon_root/test_beacon_root_contract.py @@ -130,29 +130,16 @@ def test_beacon_root_contract_calls( @pytest.mark.parametrize( "system_address_balance", [ - pytest.param( - 0, - id="empty_system_address", - marks=pytest.mark.pre_alloc_group( - "beacon_root_empty_system", - reason="Tests with empty system address balance", - ), - ), + pytest.param(0, id="empty_system_address"), pytest.param( 1, id="one_wei_system_address", - marks=pytest.mark.pre_alloc_group( - "beacon_root_one_wei_system", - reason="Tests with 1 wei system address balance", - ), + marks=pytest.mark.pre_fund_address(), ), pytest.param( int(1e18), id="one_eth_system_address", - marks=pytest.mark.pre_alloc_group( - "beacon_root_one_eth_system", - reason="Tests with 1 ETH system address balance", - ), + marks=pytest.mark.pre_fund_address(), ), ], ) diff --git a/tests/cancun/eip6780_selfdestruct/test_dynamic_create2_selfdestruct_collision.py b/tests/cancun/eip6780_selfdestruct/test_dynamic_create2_selfdestruct_collision.py index aefc864e9bc..6f830bdf073 100644 --- a/tests/cancun/eip6780_selfdestruct/test_dynamic_create2_selfdestruct_collision.py +++ b/tests/cancun/eip6780_selfdestruct/test_dynamic_create2_selfdestruct_collision.py @@ -31,9 +31,7 @@ @pytest.mark.parametrize( "create2_dest_already_in_state", ( - pytest.param( - True, marks=pytest.mark.execute(pytest.mark.skip("Modifies pre")) - ), + pytest.param(True, marks=pytest.mark.pre_address_set_to_account), False, ), ) @@ -263,9 +261,7 @@ def test_dynamic_create2_selfdestruct_collision( @pytest.mark.parametrize( "create2_dest_already_in_state", ( - pytest.param( - True, marks=pytest.mark.execute(pytest.mark.skip("Modifies pre")) - ), + pytest.param(True, marks=pytest.mark.pre_address_set_to_account), False, ), ) diff --git a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py index 6d30363b3e9..62a24b7c5eb 100644 --- a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py +++ b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py @@ -188,7 +188,13 @@ def selfdestruct_code( ], indirect=["sendall_recipient_addresses"], ) -@pytest.mark.parametrize("selfdestruct_contract_initial_balance", [0, 100_000]) +@pytest.mark.parametrize( + "selfdestruct_contract_initial_balance", + [ + 0, + pytest.param(100_000, marks=pytest.mark.pre_fund_address()), + ], +) @pytest.mark.valid_from("Shanghai") def test_create_selfdestruct_same_tx( state_test: StateTestFiller, @@ -358,7 +364,10 @@ def test_create_selfdestruct_same_tx( @pytest.mark.parametrize("create_opcode", [Op.CREATE, Op.CREATE2]) @pytest.mark.parametrize("call_times", [0, 1]) -@pytest.mark.parametrize("selfdestruct_contract_initial_balance", [0, 100_000]) +@pytest.mark.parametrize( + "selfdestruct_contract_initial_balance", + [0, pytest.param(100_000, marks=pytest.mark.pre_fund_address())], +) @pytest.mark.valid_from("Shanghai") def test_self_destructing_initcode( state_test: StateTestFiller, @@ -487,7 +496,10 @@ def test_self_destructing_initcode( @pytest.mark.parametrize("tx_value", [0, 100_000]) -@pytest.mark.parametrize("selfdestruct_contract_initial_balance", [0, 100_000]) +@pytest.mark.parametrize( + "selfdestruct_contract_initial_balance", + [0, pytest.param(100_000, marks=pytest.mark.pre_fund_address())], +) @pytest.mark.valid_from("Shanghai") def test_self_destructing_initcode_create_tx( state_test: StateTestFiller, @@ -515,9 +527,11 @@ def test_self_destructing_initcode_create_tx( gas_limit=500_000, ) selfdestruct_contract_address = tx.created_contract - pre.fund_address( - selfdestruct_contract_address, selfdestruct_contract_initial_balance - ) + if selfdestruct_contract_initial_balance > 0: + pre.fund_address( + selfdestruct_contract_address, + selfdestruct_contract_initial_balance, + ) # Our entry point is an initcode that in turn creates a self-destructing # contract @@ -549,7 +563,10 @@ def test_self_destructing_initcode_create_tx( ], indirect=["sendall_recipient_addresses"], ) -@pytest.mark.parametrize("selfdestruct_contract_initial_balance", [0, 100_000]) +@pytest.mark.parametrize( + "selfdestruct_contract_initial_balance", + [0, pytest.param(100_000, marks=pytest.mark.pre_fund_address())], +) @pytest.mark.parametrize("recreate_times", [1]) @pytest.mark.parametrize("call_times", [1]) @pytest.mark.valid_from("Shanghai") @@ -626,9 +643,11 @@ def test_recreate_self_destructed_contract_different_txs( initcode=selfdestruct_contract_initcode, opcode=create_opcode, ) - pre.fund_address( - selfdestruct_contract_address, selfdestruct_contract_initial_balance - ) + if selfdestruct_contract_initial_balance > 0: + pre.fund_address( + selfdestruct_contract_address, + selfdestruct_contract_initial_balance, + ) for i in range(len(sendall_recipient_addresses)): if sendall_recipient_addresses[i] == SELF_ADDRESS: sendall_recipient_addresses[i] = selfdestruct_contract_address @@ -964,7 +983,10 @@ def test_selfdestruct_created_same_block_different_tx( @pytest.mark.parametrize("call_times", [1]) -@pytest.mark.parametrize("selfdestruct_contract_initial_balance", [0, 1]) +@pytest.mark.parametrize( + "selfdestruct_contract_initial_balance", + [0, pytest.param(1, marks=pytest.mark.pre_fund_address())], +) @pytest.mark.parametrize("call_opcode", [Op.DELEGATECALL, Op.CALLCODE]) @pytest.mark.parametrize("create_opcode", [Op.CREATE]) @pytest.mark.valid_from("Shanghai") @@ -998,9 +1020,11 @@ def test_calling_from_new_contract_to_pre_existing_contract( address=entry_code_address, nonce=1 ) - pre.fund_address( - selfdestruct_contract_address, selfdestruct_contract_initial_balance - ) + if selfdestruct_contract_initial_balance > 0: + pre.fund_address( + selfdestruct_contract_address, + selfdestruct_contract_initial_balance, + ) # self-destructing call selfdestruct_code = call_opcode(address=pre_existing_selfdestruct_address) @@ -1263,7 +1287,10 @@ def test_calling_from_pre_existing_contract_to_new_contract( @pytest.mark.parametrize("create_opcode", [Op.CREATE, Op.CREATE2]) -@pytest.mark.parametrize("selfdestruct_contract_initial_balance", [0, 100_000]) +@pytest.mark.parametrize( + "selfdestruct_contract_initial_balance", + [0, pytest.param(100_000, marks=pytest.mark.pre_fund_address())], +) @pytest.mark.parametrize( "call_times,sendall_recipient_addresses", [ diff --git a/tests/frontier/validation/test_transaction.py b/tests/frontier/validation/test_transaction.py index 33fd69ff131..315c4c5a6ba 100644 --- a/tests/frontier/validation/test_transaction.py +++ b/tests/frontier/validation/test_transaction.py @@ -64,6 +64,7 @@ def test_tx_gas_limit( ), ], ) +@pytest.mark.pre_eoa_with_hardcoded_nonce def test_tx_nonce( blockchain_test: BlockchainTestFiller, pre: Alloc, diff --git a/tests/paris/eip7610_create_collision/test_initcollision.py b/tests/paris/eip7610_create_collision/test_initcollision.py index 8c25f64674d..aba13b83411 100644 --- a/tests/paris/eip7610_create_collision/test_initcollision.py +++ b/tests/paris/eip7610_create_collision/test_initcollision.py @@ -56,7 +56,7 @@ ], ), # We need to modify the pre-alloc to include the collision - pytest.mark.pre_alloc_modify, + pytest.mark.pre_address_set_to_account, ] diff --git a/tests/paris/eip7610_create_collision/test_revert_in_create.py b/tests/paris/eip7610_create_collision/test_revert_in_create.py index f8834ea81ec..4bd731d454f 100644 --- a/tests/paris/eip7610_create_collision/test_revert_in_create.py +++ b/tests/paris/eip7610_create_collision/test_revert_in_create.py @@ -20,7 +20,7 @@ pytestmark = [ pytest.mark.valid_from("Paris"), # We need to modify the pre-alloc to include the collision - pytest.mark.pre_alloc_modify, + pytest.mark.pre_address_set_to_account, ] diff --git a/tests/prague/eip6110_deposits/test_modified_contract.py b/tests/prague/eip6110_deposits/test_modified_contract.py index 8ab56a6775a..54802151ed7 100644 --- a/tests/prague/eip6110_deposits/test_modified_contract.py +++ b/tests/prague/eip6110_deposits/test_modified_contract.py @@ -22,7 +22,7 @@ pytestmark = [ pytest.mark.valid_from("Prague"), - pytest.mark.execute(pytest.mark.skip(reason="modifies pre-alloc")), + pytest.mark.pre_address_set_to_account(), ] REFERENCE_SPEC_GIT_PATH = ref_spec_6110.git_path @@ -73,38 +73,10 @@ @pytest.mark.parametrize( "include_deposit_event,extra_event_type", [ - pytest.param( - True, - "transfer_log", - marks=pytest.mark.pre_alloc_group( - "deposit_extra_logs_with_event_transfer", - reason="Deposit contract with Transfer log AND deposit event", - ), - ), - pytest.param( - True, - "no_topics", - marks=pytest.mark.pre_alloc_group( - "deposit_extra_logs_with_event_no_topics", - reason="Deposit contract with no-topics log AND deposit event", - ), - ), - pytest.param( - False, - "transfer_log", - marks=pytest.mark.pre_alloc_group( - "deposit_extra_logs_no_event_transfer", - reason="Deposit contract with Transfer log NO deposit event", - ), - ), - pytest.param( - False, - "no_topics", - marks=pytest.mark.pre_alloc_group( - "deposit_extra_logs_no_event_no_topics", - reason="Deposit contract with no-topics log NO deposit event", - ), - ), + pytest.param(True, "transfer_log"), + pytest.param(True, "no_topics"), + pytest.param(False, "transfer_log"), + pytest.param(False, "no_topics"), ], ) def test_extra_logs( @@ -205,14 +177,7 @@ def test_extra_logs( @pytest.mark.parametrize( "log_argument,value", [ - pytest.param( - arg, - val, - marks=pytest.mark.pre_alloc_group( - f"deposit_layout_{arg}_{val}", - reason=f"Deposit contract with invalid {arg} set to {val}", - ), - ) + pytest.param(arg, val) for arg in EVENT_ARGUMENTS for val in EVENT_ARGUMENT_VALUES ], @@ -266,25 +231,7 @@ def test_invalid_layout( ) -@pytest.mark.parametrize( - "slice_bytes", - [ - pytest.param( - True, - marks=pytest.mark.pre_alloc_group( - "deposit_log_length_short", - reason="Deposit contract with shortened log data", - ), - ), - pytest.param( - False, - marks=pytest.mark.pre_alloc_group( - "deposit_log_length_long", - reason="Deposit contract with lengthened log data", - ), - ), - ], -) +@pytest.mark.parametrize("slice_bytes", [True, False]) @pytest.mark.exception_test def test_invalid_log_length( blockchain_test: BlockchainTestFiller, pre: Alloc, slice_bytes: bool diff --git a/tests/prague/eip7002_el_triggerable_withdrawals/test_modified_withdrawal_contract.py b/tests/prague/eip7002_el_triggerable_withdrawals/test_modified_withdrawal_contract.py index 6e351bfd603..6af07533127 100644 --- a/tests/prague/eip7002_el_triggerable_withdrawals/test_modified_withdrawal_contract.py +++ b/tests/prague/eip7002_el_triggerable_withdrawals/test_modified_withdrawal_contract.py @@ -29,7 +29,10 @@ REFERENCE_SPEC_GIT_PATH: str = ref_spec_7002.git_path REFERENCE_SPEC_VERSION: str = ref_spec_7002.version -pytestmark: pytest.MarkDecorator = pytest.mark.valid_from("Prague") +pytestmark: List[pytest.MarkDecorator] = [ + pytest.mark.valid_from("Prague"), + pytest.mark.pre_address_set_to_account(), +] def withdrawal_list_with_custom_fee(n: int) -> List[WithdrawalRequest]: # noqa: D103 @@ -82,9 +85,6 @@ def withdrawal_list_with_custom_fee(n: int) -> List[WithdrawalRequest]: # noqa: ), ], ) -@pytest.mark.pre_alloc_group( - "separate", reason="Deploys custom withdrawal contract bytecode" -) def test_extra_withdrawals( blockchain_test: BlockchainTestFiller, pre: Alloc, @@ -144,9 +144,6 @@ def test_extra_withdrawals( "system_contract", [Address(Spec_EIP7002.WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS)], ) -@pytest.mark.pre_alloc_group( - "separate", reason="Deploys custom withdrawal contract bytecode" -) @generate_system_contract_error_test( # type: ignore[arg-type] max_gas_limit=Spec_EIP7002.SYSTEM_CALL_GAS_LIMIT, ) diff --git a/tests/prague/eip7251_consolidations/test_modified_consolidation_contract.py b/tests/prague/eip7251_consolidations/test_modified_consolidation_contract.py index 4e31c166bf2..319a015f098 100644 --- a/tests/prague/eip7251_consolidations/test_modified_consolidation_contract.py +++ b/tests/prague/eip7251_consolidations/test_modified_consolidation_contract.py @@ -29,7 +29,10 @@ REFERENCE_SPEC_GIT_PATH: str = ref_spec_7251.git_path REFERENCE_SPEC_VERSION: str = ref_spec_7251.version -pytestmark: pytest.MarkDecorator = pytest.mark.valid_from("Prague") +pytestmark: List[pytest.MarkDecorator] = [ + pytest.mark.valid_from("Prague"), + pytest.mark.pre_address_set_to_account(), +] def consolidation_list_with_custom_fee(n: int) -> List[ConsolidationRequest]: # noqa: D103 @@ -82,9 +85,6 @@ def consolidation_list_with_custom_fee(n: int) -> List[ConsolidationRequest]: # ), ], ) -@pytest.mark.pre_alloc_group( - "separate", reason="Deploys custom consolidation contract bytecode" -) def test_extra_consolidations( blockchain_test: BlockchainTestFiller, pre: Alloc, @@ -145,9 +145,6 @@ def test_extra_consolidations( "system_contract", [Address(Spec_EIP7251.CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS)], ) -@pytest.mark.pre_alloc_group( - "separate", reason="Deploys custom consolidation contract bytecode" -) @generate_system_contract_error_test( # type: ignore[arg-type] max_gas_limit=Spec_EIP7251.SYSTEM_CALL_GAS_LIMIT, ) diff --git a/tests/prague/eip7702_set_code_tx/test_gas.py b/tests/prague/eip7702_set_code_tx/test_gas.py index a3ff3b1d076..c78d688c0e6 100644 --- a/tests/prague/eip7702_set_code_tx/test_gas.py +++ b/tests/prague/eip7702_set_code_tx/test_gas.py @@ -722,14 +722,7 @@ def gas_test_parameter_args( { "authority_type": AddressType.CONTRACT, }, - marks=[ - pytest.mark.pre_alloc_modify, - pytest.mark.execute( - pytest.mark.skip( - reason="Requires contract-eoa address collision" - ) - ), - ], + marks=[pytest.mark.pre_eoa_with_code], id="single_valid_authorization_invalid_contract_authority", ), pytest.param( @@ -741,14 +734,7 @@ def gas_test_parameter_args( ], "authorizations_count": multiple_authorizations_count, }, - marks=[ - pytest.mark.pre_alloc_modify, - pytest.mark.execute( - pytest.mark.skip( - reason="Requires contract-eoa address collision" - ) - ), - ], + marks=[pytest.mark.pre_eoa_with_code], id="multiple_authorizations_empty_account_then_contract_authority", ), pytest.param( @@ -757,14 +743,7 @@ def gas_test_parameter_args( "authority_type": [AddressType.EOA, AddressType.CONTRACT], "authorizations_count": multiple_authorizations_count, }, - marks=[ - pytest.mark.pre_alloc_modify, - pytest.mark.execute( - pytest.mark.skip( - reason="Requires contract-eoa address collision" - ) - ), - ], + marks=[pytest.mark.pre_eoa_with_code], id="multiple_authorizations_eoa_then_contract_authority", ), pytest.param( @@ -774,14 +753,7 @@ def gas_test_parameter_args( "authority_type": [AddressType.EOA, AddressType.CONTRACT], "authorizations_count": multiple_authorizations_count, }, - marks=[ - pytest.mark.pre_alloc_modify, - pytest.mark.execute( - pytest.mark.skip( - reason="Requires contract-eoa address collision" - ) - ), - ], + marks=[pytest.mark.pre_eoa_with_code], id="multiple_authorizations_eoa_self_sponsored_then_contract_authority", ), ] diff --git a/tests/prague/eip7702_set_code_tx/test_set_code_txs.py b/tests/prague/eip7702_set_code_tx/test_set_code_txs.py index 78ef3b8e2e8..031cdb8f38b 100644 --- a/tests/prague/eip7702_set_code_tx/test_set_code_txs.py +++ b/tests/prague/eip7702_set_code_tx/test_set_code_txs.py @@ -53,13 +53,7 @@ REFERENCE_SPEC_GIT_PATH = ref_spec_7702.git_path REFERENCE_SPEC_VERSION = ref_spec_7702.version -pytestmark = [ - pytest.mark.valid_from("Prague"), - pytest.mark.pre_alloc_group( - "set_code_tests", - reason="Tests EIP-7702 set code transactions with system contracts", - ), -] +pytestmark = pytest.mark.valid_from("Prague") auth_account_start_balance = 0 @@ -2610,17 +2604,11 @@ def test_valid_tx_invalid_chain_id( Spec.MAX_NONCE, Spec.MAX_NONCE, id="nonce=2**64-1", - marks=pytest.mark.execute( - pytest.mark.skip(reason="Impossible account nonce") - ), ), pytest.param( Spec.MAX_NONCE - 1, Spec.MAX_NONCE - 1, id="nonce=2**64-2", - marks=pytest.mark.execute( - pytest.mark.skip(reason="Impossible account nonce") - ), ), pytest.param( 0, @@ -2634,7 +2622,7 @@ def test_valid_tx_invalid_chain_id( ), ], ) -@pytest.mark.execute(pytest.mark.skip(reason="Non-zero nonce not supported")) +@pytest.mark.pre_eoa_with_hardcoded_nonce() def test_nonce_validity( state_test: StateTestFiller, pre: Alloc, @@ -2706,7 +2694,7 @@ def test_nonce_validity( ) -@pytest.mark.execute(pytest.mark.skip(reason="Impossible account nonce")) +@pytest.mark.pre_eoa_with_hardcoded_nonce() def test_nonce_overflow_after_first_authorization( state_test: StateTestFiller, pre: Alloc, @@ -3994,9 +3982,8 @@ def test_authorization_reusing_nonce( [True, False], ) @pytest.mark.exception_test -@pytest.mark.execute( - pytest.mark.skip(reason="Requires contract-eoa address collision") -) +@pytest.mark.pre_eoa_with_code +@pytest.mark.pre_eoa_with_hardcoded_nonce def test_set_code_from_account_with_non_delegating_code( state_test: StateTestFiller, pre: Alloc, diff --git a/tests/tangerine_whistle/eip150_operation_gas_costs/test_eip150_selfdestruct.py b/tests/tangerine_whistle/eip150_operation_gas_costs/test_eip150_selfdestruct.py index df3674129b4..8d329e466ec 100644 --- a/tests/tangerine_whistle/eip150_operation_gas_costs/test_eip150_selfdestruct.py +++ b/tests/tangerine_whistle/eip150_operation_gas_costs/test_eip150_selfdestruct.py @@ -576,19 +576,11 @@ def test_selfdestruct_state_access_boundary( @pytest.mark.parametrize( "beneficiary_initial_balance", [ - pytest.param( - 0, - id="dead_beneficiary", - marks=pytest.mark.pre_alloc_group( - "eip150_selfdestruct_precompile_dead" - ), - ), + pytest.param(0, id="dead_beneficiary"), pytest.param( 1, id="alive_beneficiary", - marks=pytest.mark.pre_alloc_group( - "eip150_selfdestruct_precompile_alive" - ), + marks=pytest.mark.pre_fund_address(), ), ], ) @@ -697,19 +689,11 @@ def test_selfdestruct_to_precompile( @pytest.mark.parametrize( "beneficiary_initial_balance", [ - pytest.param( - 0, - id="dead_beneficiary", - marks=pytest.mark.pre_alloc_group( - "eip150_selfdestruct_precompile_boundary_dead" - ), - ), + pytest.param(0, id="dead_beneficiary"), pytest.param( 1, id="alive_beneficiary", - marks=pytest.mark.pre_alloc_group( - "eip150_selfdestruct_precompile_boundary_alive" - ), + marks=pytest.mark.pre_fund_address(), ), ], ) From 6fc27b40d4adc6cc425f04c58a6810a5faef9bc4 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Sat, 7 Feb 2026 19:23:55 +0000 Subject: [PATCH 08/22] fix(testing): test generator use appropriate mark --- .../src/execution_testing/tools/utility/generators.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/testing/src/execution_testing/tools/utility/generators.py b/packages/testing/src/execution_testing/tools/utility/generators.py index 8819dfe04ad..d35182f1e0b 100644 --- a/packages/testing/src/execution_testing/tools/utility/generators.py +++ b/packages/testing/src/execution_testing/tools/utility/generators.py @@ -179,7 +179,8 @@ def decorator(func: SystemContractDeployTestFunction) -> Callable: ], ids=lambda x: x.name.lower(), ) - @pytest.mark.execute(pytest.mark.skip(reason="modifies pre-alloc")) + @pytest.mark.pre_address_set_to_account + @pytest.mark.pre_fund_address @pytest.mark.valid_at_transition_to(fork.name()) def wrapper( blockchain_test: BlockchainTestFiller, @@ -248,9 +249,7 @@ def wrapper( nonce=0, balance=balance, ) - pre[deployer_address] = Account( - balance=deployer_required_balance, - ) + pre.fund_address(deployer_address, deployer_required_balance) expected_deploy_address_int = int.from_bytes( expected_deploy_address, "big" From b9af4fb0b6b85aca40f631cf19d033cd9bd4bcd0 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Sat, 7 Feb 2026 19:31:35 +0000 Subject: [PATCH 09/22] fix(testing/filler): Logic --- .../cli/pytest_commands/plugins/filler/filler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py index 4c2e26e0ef2..14ef295db79 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py @@ -1429,7 +1429,7 @@ def compute_pre_alloc_group_hash( else: group_salt = "separate" else: - if not alloc_flags.incompatible_with_alloc_grouping(): + if alloc_flags.incompatible_with_alloc_grouping(): group_salt = "separate" if group_salt: From 05de7facb315e5c20fd1aeed9f7404d1eac0568d Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Sat, 7 Feb 2026 19:45:49 +0000 Subject: [PATCH 10/22] refactor(tests): Remove most pre-alloc-group markers --- .../test_block_access_lists.py | 4 ---- .../test_block_access_lists_eip4895.py | 4 ---- .../test_block_access_lists_eip7251.py | 4 ---- .../test_beacon_root_contract.py | 20 ++----------------- .../test_max_block_rlp_size.py | 8 +------- .../test_contract_deployment.py | 5 ----- .../prague/eip6110_deposits/test_deposits.py | 8 -------- .../test_contract_deployment.py | 5 ----- .../test_withdrawal_requests.py | 8 -------- .../test_withdrawal_requests_during_fork.py | 5 ++--- .../test_consolidations.py | 8 -------- .../test_consolidations_during_fork.py | 6 ++---- .../test_contract_deployment.py | 5 ----- .../test_multi_type_requests.py | 16 --------------- 14 files changed, 7 insertions(+), 99 deletions(-) diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py index a54aa9c8655..8657ab5e3f7 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py @@ -1387,10 +1387,6 @@ def test_bal_coinbase_zero_tip( ) -@pytest.mark.pre_alloc_group( - "precompile_funded", - reason="Expects clean precompile balances, isolate in EngineX", -) @pytest.mark.parametrize( "value", [ diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip4895.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip4895.py index df35ba66ef5..5f1781869d4 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip4895.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip4895.py @@ -623,10 +623,6 @@ def test_bal_zero_withdrawal( ) -@pytest.mark.pre_alloc_group( - "withdrawal_to_precompiles", - reason="Expects clean precompile balances, isolate in EngineX", -) @pytest.mark.parametrize_by_fork( "precompile", lambda fork: [ diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7251.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7251.py index a3ed19d9578..c6be6cdecdd 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7251.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7251.py @@ -93,10 +93,6 @@ ), ], ) -@pytest.mark.pre_alloc_group( - "consolidation_requests", - reason="Tests standard consolidation request functionality", -) def test_bal_system_dequeue_consolidations_eip7251( blockchain_test: BlockchainTestFiller, pre: Alloc, diff --git a/tests/cancun/eip4788_beacon_root/test_beacon_root_contract.py b/tests/cancun/eip4788_beacon_root/test_beacon_root_contract.py index c142e049941..96bdeffdd33 100644 --- a/tests/cancun/eip4788_beacon_root/test_beacon_root_contract.py +++ b/tests/cancun/eip4788_beacon_root/test_beacon_root_contract.py @@ -49,12 +49,6 @@ def count_factory(start: int, step: int = 1) -> Callable[[], Iterator[int]]: return lambda: count(start, step) -pytestmark = pytest.mark.pre_alloc_group( - "beacon_root_tests", - reason="Tests beacon root contract functionality using system contract", -) - - @pytest.mark.parametrize( "call_gas, valid_call", [ @@ -684,10 +678,7 @@ def test_beacon_root_transition( @pytest.mark.parametrize("timestamp", [15_000]) @pytest.mark.valid_at_transition_to("Cancun") -@pytest.mark.pre_alloc_group( - "beacon_root_no_contract", - reason="This test removes the beacon root system contract", -) +@pytest.mark.pre_address_set_to_account() def test_no_beacon_root_contract_at_transition( blockchain_test: BlockchainTestFiller, pre: Alloc, @@ -766,14 +757,7 @@ def test_no_beacon_root_contract_at_transition( ], ) @pytest.mark.valid_at_transition_to("Cancun") -@pytest.mark.pre_alloc_group( - "beacon_root_deploy_contract", - reason=( - "This test is parametrized with a hard-coded address (the beacon root " - "contract deployer address); they can't be in the same pre alloc " - "group." - ), -) +@pytest.mark.pre_address_set_to_account() def test_beacon_root_contract_deploy( blockchain_test: BlockchainTestFiller, pre: Alloc, diff --git a/tests/osaka/eip7934_block_rlp_limit/test_max_block_rlp_size.py b/tests/osaka/eip7934_block_rlp_limit/test_max_block_rlp_size.py index a32ba9a57e0..180d5a405ff 100644 --- a/tests/osaka/eip7934_block_rlp_limit/test_max_block_rlp_size.py +++ b/tests/osaka/eip7934_block_rlp_limit/test_max_block_rlp_size.py @@ -35,13 +35,7 @@ REFERENCE_SPEC_GIT_PATH = ref_spec_7934.git_path REFERENCE_SPEC_VERSION = ref_spec_7934.version -pytestmark = [ - pytest.mark.pre_alloc_group( - "block_rlp_limit_tests", - reason="Block RLP size tests require exact calculations", - ), - pytest.mark.xdist_group(name="bigmem"), -] +pytestmark = pytest.mark.xdist_group(name="bigmem") HEADER_TIMESTAMP = 123456789 diff --git a/tests/prague/eip2935_historical_block_hashes_from_state/test_contract_deployment.py b/tests/prague/eip2935_historical_block_hashes_from_state/test_contract_deployment.py index 79b78d5158a..980980acb4e 100644 --- a/tests/prague/eip2935_historical_block_hashes_from_state/test_contract_deployment.py +++ b/tests/prague/eip2935_historical_block_hashes_from_state/test_contract_deployment.py @@ -6,7 +6,6 @@ from pathlib import Path from typing import Dict, Generator -import pytest from execution_testing import ( Account, Address, @@ -25,10 +24,6 @@ REFERENCE_SPEC_VERSION = ref_spec_2935.version -@pytest.mark.pre_alloc_group( - "separate", - reason="Deploys history storage system contract at hardcoded address", -) @generate_system_contract_deploy_test( fork=Prague, tx_json_path=Path(realpath(__file__)).parent / "contract_deploy_tx.json", diff --git a/tests/prague/eip6110_deposits/test_deposits.py b/tests/prague/eip6110_deposits/test_deposits.py index 7f69c23ee9c..f0299c2a855 100644 --- a/tests/prague/eip6110_deposits/test_deposits.py +++ b/tests/prague/eip6110_deposits/test_deposits.py @@ -915,10 +915,6 @@ ], ) @pytest.mark.slow() -@pytest.mark.pre_alloc_group( - "deposit_requests", - reason="Tests standard deposit request functionality with system contract", -) def test_deposit( blockchain_test: BlockchainTestFiller, pre: Alloc, @@ -1178,10 +1174,6 @@ def test_deposit( ], ) @pytest.mark.exception_test -@pytest.mark.pre_alloc_group( - "deposit_requests", - reason="Tests standard deposit request functionality with system contract", -) def test_deposit_negative( blockchain_test: BlockchainTestFiller, pre: Alloc, diff --git a/tests/prague/eip7002_el_triggerable_withdrawals/test_contract_deployment.py b/tests/prague/eip7002_el_triggerable_withdrawals/test_contract_deployment.py index 98e5b83267d..8978925c6e4 100644 --- a/tests/prague/eip7002_el_triggerable_withdrawals/test_contract_deployment.py +++ b/tests/prague/eip7002_el_triggerable_withdrawals/test_contract_deployment.py @@ -6,7 +6,6 @@ from pathlib import Path from typing import Any, Generator -import pytest from execution_testing import ( Address, Alloc, @@ -25,10 +24,6 @@ REFERENCE_SPEC_VERSION = ref_spec_7002.version -@pytest.mark.pre_alloc_group( - "separate", - reason="Deploys withdrawal system contract at hardcoded predeploy address", -) @generate_system_contract_deploy_test( fork=Prague, tx_json_path=Path(realpath(__file__)).parent / "contract_deploy_tx.json", 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 3f1a2f90a12..6bdeaa5a1f7 100644 --- a/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests.py +++ b/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests.py @@ -662,10 +662,6 @@ ), ], ) -@pytest.mark.pre_alloc_group( - "withdrawal_requests", - reason="Tests standard withdrawal request functionality", -) def test_withdrawal_requests( blockchain_test: BlockchainTestFiller, blocks: List[Block], @@ -839,10 +835,6 @@ def test_withdrawal_requests( ], ) @pytest.mark.exception_test -@pytest.mark.pre_alloc_group( - "withdrawal_requests", - reason="Tests standard withdrawal request functionality", -) def test_withdrawal_requests_negative( pre: Alloc, fork: Fork, diff --git a/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests_during_fork.py b/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests_during_fork.py index 72d72e8ff30..0fe76103bb3 100644 --- a/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests_during_fork.py +++ b/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests_during_fork.py @@ -81,9 +81,8 @@ ], ) @pytest.mark.parametrize("timestamp", [15_000 - BLOCKS_BEFORE_FORK], ids=[""]) -@pytest.mark.pre_alloc_group( - "separate", reason="Deploys withdrawal system contract at fork transition" -) +@pytest.mark.pre_address_set_to_account +@pytest.mark.pre_fund_address def test_withdrawal_requests_during_fork( blockchain_test: BlockchainTestFiller, blocks: List[Block], diff --git a/tests/prague/eip7251_consolidations/test_consolidations.py b/tests/prague/eip7251_consolidations/test_consolidations.py index 5d4c1fe13ae..52eb22c57ef 100644 --- a/tests/prague/eip7251_consolidations/test_consolidations.py +++ b/tests/prague/eip7251_consolidations/test_consolidations.py @@ -677,10 +677,6 @@ ), ], ) -@pytest.mark.pre_alloc_group( - "consolidation_requests", - reason="Tests standard consolidation request functionality", -) def test_consolidation_requests( blockchain_test: BlockchainTestFiller, blocks: List[Block], @@ -876,10 +872,6 @@ def test_consolidation_requests( ], ) @pytest.mark.exception_test -@pytest.mark.pre_alloc_group( - "consolidation_requests", - reason="Tests standard consolidation request functionality", -) def test_consolidation_requests_negative( pre: Alloc, fork: Fork, diff --git a/tests/prague/eip7251_consolidations/test_consolidations_during_fork.py b/tests/prague/eip7251_consolidations/test_consolidations_during_fork.py index f8208d4064d..3133afcbf4d 100644 --- a/tests/prague/eip7251_consolidations/test_consolidations_during_fork.py +++ b/tests/prague/eip7251_consolidations/test_consolidations_during_fork.py @@ -81,10 +81,8 @@ ], ) @pytest.mark.parametrize("timestamp", [15_000 - BLOCKS_BEFORE_FORK], ids=[""]) -@pytest.mark.pre_alloc_group( - "separate", - reason="Deploys consolidation system contract at fork transition", -) +@pytest.mark.pre_address_set_to_account +@pytest.mark.pre_fund_address def test_consolidation_requests_during_fork( blockchain_test: BlockchainTestFiller, blocks: List[Block], diff --git a/tests/prague/eip7251_consolidations/test_contract_deployment.py b/tests/prague/eip7251_consolidations/test_contract_deployment.py index 4bedc48f4c7..f0967a8306c 100644 --- a/tests/prague/eip7251_consolidations/test_contract_deployment.py +++ b/tests/prague/eip7251_consolidations/test_contract_deployment.py @@ -6,7 +6,6 @@ from pathlib import Path from typing import Any, Generator -import pytest from execution_testing import ( Address, Alloc, @@ -25,10 +24,6 @@ REFERENCE_SPEC_VERSION = ref_spec_7251.version -@pytest.mark.pre_alloc_group( - "separate", - reason="Deploys consolidation system contract at hardcoded address", -) @generate_system_contract_deploy_test( fork=Prague, tx_json_path=Path(realpath(__file__)).parent / "contract_deploy_tx.json", diff --git a/tests/prague/eip7685_general_purpose_el_requests/test_multi_type_requests.py b/tests/prague/eip7685_general_purpose_el_requests/test_multi_type_requests.py index 2a164790b9d..c6bc3bc17b8 100644 --- a/tests/prague/eip7685_general_purpose_el_requests/test_multi_type_requests.py +++ b/tests/prague/eip7685_general_purpose_el_requests/test_multi_type_requests.py @@ -336,10 +336,6 @@ def get_contract_permutations( ), ], ) -@pytest.mark.pre_alloc_group( - "multi_type_requests", - reason="Tests combinations of multiple request types", -) def test_valid_multi_type_requests( blockchain_test: BlockchainTestFiller, pre: Alloc, @@ -358,10 +354,6 @@ def test_valid_multi_type_requests( @pytest.mark.parametrize("requests", [*get_permutations()]) -@pytest.mark.pre_alloc_group( - "multi_type_requests", - reason="Tests combinations of multiple request types", -) def test_valid_multi_type_request_from_same_tx( blockchain_test: BlockchainTestFiller, pre: Alloc, @@ -672,10 +664,6 @@ def func(fork: Fork) -> List[ParameterSet]: invalid_requests_block_combinations(correct_requests_hash_in_header=False), ) @pytest.mark.exception_test -@pytest.mark.pre_alloc_group( - "multi_type_requests", - reason="Tests combinations of multiple request types", -) def test_invalid_multi_type_requests( blockchain_test: BlockchainTestFiller, pre: Alloc, @@ -705,10 +693,6 @@ def test_invalid_multi_type_requests( @pytest.mark.parametrize("correct_requests_hash_in_header", [True]) @pytest.mark.blockchain_test_engine_only @pytest.mark.exception_test -@pytest.mark.pre_alloc_group( - "multi_type_requests", - reason="Tests combinations of multiple request types", -) def test_invalid_multi_type_requests_engine( blockchain_test: BlockchainTestFiller, pre: Alloc, From 88e3592a937dcedbe4a833bd1c78e23e2fb08401 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Sat, 7 Feb 2026 20:47:22 +0000 Subject: [PATCH 11/22] feat(filler): Make pre-alloc auto-groupable --- .../pytest_commands/plugins/filler/filler.py | 125 +++++------------- .../plugins/filler/pre_alloc.py | 59 +++++++++ .../plugins/shared/pre_alloc.py | 22 +-- 3 files changed, 103 insertions(+), 103 deletions(-) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py index 14ef295db79..5c260b5c114 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py @@ -10,7 +10,6 @@ import configparser import datetime import gc -import hashlib import json import logging import os @@ -31,9 +30,9 @@ from execution_testing.base_types import ( Account, Address, - Alloc, ReferenceSpec, ) +from execution_testing.base_types import Alloc as BaseAlloc from execution_testing.cli.gen_index import ( merge_partial_indexes, ) @@ -78,11 +77,11 @@ is_help_or_collectonly_mode, labeled_format_parameter_set, ) -from ..shared.pre_alloc import AllocFlags from ..spec_version_checker.spec_version_checker import ( get_ref_spec_from_module, ) from .fixture_output import FixtureOutput +from .pre_alloc import Alloc # Fixture output dir for keyboard interrupt cleanup (set in pytest_configure). # Used by _merge_on_exit to merge partial JSONL files on Ctrl+C or SIGTERM. @@ -425,8 +424,8 @@ def save_pre_alloc_groups(self) -> None: def calculate_post_state_diff( - post_state: Alloc, genesis_state: Alloc -) -> Alloc: + post_state: BaseAlloc, genesis_state: BaseAlloc +) -> BaseAlloc: """ Calculate the state difference between post_state and genesis_state. @@ -472,7 +471,7 @@ def calculate_post_state_diff( # Account unchanged - don't include in diff - return Alloc(diff) + return BaseAlloc(diff) def default_output_directory() -> str: @@ -1397,83 +1396,6 @@ def fixture_source_url( return github_url -def compute_pre_alloc_group_hash( - *, - base_test: BaseTest, - alloc_flags: AllocFlags, -) -> str: - """Hash (fork, env) in order to group tests by genesis config.""" - if not hasattr(base_test, "pre"): - raise AttributeError( - f"{base_test.__class__.__name__} does not have a 'pre' field. " - "Pre-allocation group usage is only supported for test " - "types that define pre-allocs." - ) - fork_digest = hashlib.sha256( - base_test.fork.name().encode("utf-8") - ).digest() - fork_hash = int.from_bytes(fork_digest[:8], byteorder="big") - genesis_env = base_test.get_genesis_environment() - combined_hash = fork_hash ^ hash(genesis_env) - - # Check if test has pre_alloc_group marker - if base_test._request is not None and hasattr(base_test._request, "node"): - pre_alloc_group_marker = base_test._request.node.get_closest_marker( - "pre_alloc_group" - ) - group_salt: str = "" - if pre_alloc_group_marker: - # Get the group name/salt from marker args - if pre_alloc_group_marker.args: - group_salt = str(pre_alloc_group_marker.args[0]) - else: - group_salt = "separate" - else: - if alloc_flags.incompatible_with_alloc_grouping(): - group_salt = "separate" - - if group_salt: - if group_salt == "separate": - # Use nodeid for unique group per test - group_salt = base_test._request.node.nodeid - # Add custom salt to hash - salt_hash = hashlib.sha256(group_salt.encode("utf-8")).digest() - salt_int = int.from_bytes(salt_hash[:8], byteorder="big") - combined_hash = combined_hash ^ salt_int - - return f"0x{combined_hash:016x}" - - -def update_pre_alloc_groups( - *, - base_test: BaseTest, - pre_alloc_group_builders: PreAllocGroupBuilders, - test_id: str, - alloc_flags: AllocFlags = AllocFlags.NONE, -) -> None: - """ - Create or update the pre-allocation group with the pre from the current - spec. - """ - if not hasattr(base_test, "pre"): - raise AttributeError( - f"{base_test.__class__.__name__} does not have a 'pre' field. " - "Pre-allocation groups are only supported for test types " - "that define pre-allocation." - ) - pre_alloc_hash = compute_pre_alloc_group_hash( - base_test=base_test, - alloc_flags=alloc_flags, - ) - pre_alloc_group_builders.add_test_pre( - pre_alloc_hash=pre_alloc_hash, - test_id=str(test_id), - fork=base_test.fork, - environment=base_test.get_genesis_environment(), - pre=base_test.pre, - ) - - def base_test_parametrizer(cls: Type[BaseTest]) -> Any: """ Generate pytest.fixture for a given BaseTest subclass. @@ -1503,7 +1425,6 @@ def base_test_parametrizer_func( gas_benchmark_value: int, fixed_opcode_count: int | None, witness_generator: Any, - alloc_flags: AllocFlags, ) -> Any: """ Fixture used to instantiate an auto-fillable BaseTest object from @@ -1560,16 +1481,35 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: session: FillingSession = request.config.filling_session # type: ignore assert isinstance(session, FillingSession) + group_salt: str | None = None + if pre_alloc_group_marker := request.node.get_closest_marker( + "pre_alloc_group" + ): + # Get the group name/salt from marker args + if pre_alloc_group_marker.args: + group_salt = str(pre_alloc_group_marker.args[0]) + else: + # We got the marker but unspecified, pass test name + group_salt = request.node.nodeid + # Phase 1: Generate pre-allocation groups if session.phase_manager.is_pre_alloc_generation: # Use the original update_pre_alloc_groups method which # returns the groups assert session.pre_alloc_group_builders is not None - update_pre_alloc_groups( - base_test=self, - pre_alloc_group_builders=session.pre_alloc_group_builders, - test_id=request.node.nodeid, - alloc_flags=alloc_flags, + test_id = str(request.node.nodeid) + genesis_environment = self.get_genesis_environment() + pre_alloc_hash = pre.compute_pre_alloc_group_hash( + fork=fork, + genesis_environment=genesis_environment, + group_salt=group_salt, + ) + session.pre_alloc_group_builders.add_test_pre( + pre_alloc_hash=pre_alloc_hash, + test_id=test_id, + fork=fork, + environment=genesis_environment, + pre=pre, ) return # Skip fixture generation in phase 1 @@ -1580,9 +1520,10 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: FixtureFillingPhase.PRE_ALLOC_GENERATION in fixture_format.format_phases ): - pre_alloc_hash = compute_pre_alloc_group_hash( - base_test=self, - alloc_flags=alloc_flags, + pre_alloc_hash = pre.compute_pre_alloc_group_hash( + fork=fork, + genesis_environment=self.get_genesis_environment(), + group_salt=group_salt, ) group = session.get_pre_alloc_group(pre_alloc_hash) self.pre = group.pre diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py index 50bdcc74d79..8fbc084a097 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py @@ -1,5 +1,6 @@ """Pre-alloc specifically conditioned for test filling.""" +import hashlib import inspect from functools import cache from hashlib import sha256 @@ -30,6 +31,7 @@ DETERMINISTIC_FACTORY_ADDRESS, DETERMINISTIC_FACTORY_BYTECODE, EOA, + Environment, compute_deterministic_create2_address, ) from execution_testing.tools import Initcode @@ -93,6 +95,63 @@ def code_pre_processor(self, code: BytesConvertible) -> BytesConvertible: """Pre-processes the code before setting it.""" return code + def group_salt(self) -> str | None: + """ + Return a group salt if this pre-allocation was affected by + setting addresses to hard-coded accounts or has pre-funded addresses. + + Any modification the test does to a hard-coded address must affect + this salt. + """ + if ( + not self._set_addresses + and not self._pre_funded_addresses + and not self._hardcoded_addresses_deployed_to + and not self._deleted_addresses + ): + return None + + # Build a hashable buffer from the modified accounts. + buffer = b"" + altered_accounts = ( + self._set_addresses + | self._pre_funded_addresses + | self._hardcoded_addresses_deployed_to + ) + if altered_accounts: + buffer += b"\0" + for altered_account in sorted(altered_accounts): + buffer += altered_account + buffer += self[altered_account].hash() + if self._deleted_addresses: + buffer += b"\1" + for deleted_address in sorted(self._deleted_addresses): + buffer += deleted_address + + return buffer.hex() + + def compute_pre_alloc_group_hash( + self, + *, + fork: Fork, + genesis_environment: Environment, + group_salt: str | None, + ) -> str: + """Hash (fork, env) in order to group tests by genesis config.""" + fork_digest = hashlib.sha256(fork.name().encode("utf-8")).digest() + fork_hash = int.from_bytes(fork_digest[:8], byteorder="big") + combined_hash = fork_hash ^ hash(genesis_environment) + + # Check if this pre-allocation has a group salt + group_salt = group_salt or self.group_salt() + if group_salt: + # Add custom salt to hash + salt_hash = hashlib.sha256(group_salt.encode("utf-8")).digest() + salt_int = int.from_bytes(salt_hash[:8], byteorder="big") + combined_hash = combined_hash ^ salt_int + + return f"0x{combined_hash:016x}" + def _deterministic_deploy_contract( self, *, diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/pre_alloc.py index f36867b5836..0a7681c35fd 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/pre_alloc.py @@ -1,7 +1,7 @@ """Shared pre-alloc functionality.""" from enum import IntFlag, auto -from typing import Any, Literal +from typing import Any, Literal, Set from pydantic import PrivateAttr @@ -46,16 +46,6 @@ def is_mutable(self) -> bool: """Return whether the pre-alloc is mutable.""" return bool(self & AllocFlags.MUTABLE) - def incompatible_with_alloc_grouping(self) -> bool: - """Return True if the restrictions allow pre-alloc grouping.""" - if ( - AllocFlags.ALLOW_ADDRESS_SET_TO_ACCOUNT in self - or AllocFlags.ALLOW_DEPLOY_TO_HARDCODED_ADDRESS in self - or AllocFlags.ALLOW_FUND_ADDRESS in self - ): - return True - return False - def assert_allow_account_address_set(self) -> None: """ Raise an exception if the ALLOW_ADDRESS_SET_TO_ACCOUNT flag is not set. @@ -141,6 +131,12 @@ class Alloc(BaseAlloc): _fork: Fork = PrivateAttr() _flags: AllocFlags = PrivateAttr(AllocFlags.NONE) + _set_addresses: Set[Address] = PrivateAttr(default_factory=set) + _deleted_addresses: Set[Address] = PrivateAttr(default_factory=set) + _pre_funded_addresses: Set[Address] = PrivateAttr(default_factory=set) + _hardcoded_addresses_deployed_to: Set[Address] = PrivateAttr( + default_factory=set + ) def __init__( self, @@ -175,6 +171,7 @@ def __internal_setitem__( """ if not isinstance(address, Address): address = Address(address) + self._set_addresses.add(address) self.root[address] = account def __delitem__( @@ -195,6 +192,7 @@ def __internal_delitem__( """ if not isinstance(address, Address): address = Address(address) + self._deleted_addresses.add(address) self.root.pop(address, None) def deterministic_deploy_contract( @@ -270,6 +268,7 @@ def deploy_contract( """ if address is not None: self._flags.assert_allow_deploy_to_hardcoded_address() + self._hardcoded_addresses_deployed_to.add(Address(address)) if Number(nonce) == 0: self._flags.assert_allow_zero_nonce_contracts() @@ -371,6 +370,7 @@ def fund_address( """ self._flags.assert_allow_fund_address() + self._pre_funded_addresses.add(address) return self._fund_address( address=address, amount=amount, From baa132a6cb3c78744d773aa0c9124b3f755e5e91 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Sat, 7 Feb 2026 21:17:48 +0000 Subject: [PATCH 12/22] fix: bug, add unit test --- .../plugins/filler/pre_alloc.py | 19 +- .../filler/tests/test_prealloc_group.py | 205 +++++++++++++++--- .../plugins/shared/pre_alloc.py | 16 +- 3 files changed, 194 insertions(+), 46 deletions(-) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py index 8fbc084a097..0bc77ddae9b 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py @@ -95,10 +95,10 @@ def code_pre_processor(self, code: BytesConvertible) -> BytesConvertible: """Pre-processes the code before setting it.""" return code - def group_salt(self) -> str | None: + def modified_accounts_salt(self) -> int: """ - Return a group salt if this pre-allocation was affected by - setting addresses to hard-coded accounts or has pre-funded addresses. + Return a salt if this pre-allocation was affected by setting addresses + to hard-coded accounts or has pre-funded addresses. Any modification the test does to a hard-coded address must affect this salt. @@ -109,7 +109,7 @@ def group_salt(self) -> str | None: and not self._hardcoded_addresses_deployed_to and not self._deleted_addresses ): - return None + return 0 # Build a hashable buffer from the modified accounts. buffer = b"" @@ -128,7 +128,9 @@ def group_salt(self) -> str | None: for deleted_address in sorted(self._deleted_addresses): buffer += deleted_address - return buffer.hex() + return int.from_bytes( + hashlib.sha256(buffer).digest()[:8], byteorder="big" + ) def compute_pre_alloc_group_hash( self, @@ -140,10 +142,13 @@ def compute_pre_alloc_group_hash( """Hash (fork, env) in order to group tests by genesis config.""" fork_digest = hashlib.sha256(fork.name().encode("utf-8")).digest() fork_hash = int.from_bytes(fork_digest[:8], byteorder="big") - combined_hash = fork_hash ^ hash(genesis_environment) + combined_hash = ( + fork_hash + ^ hash(genesis_environment) + ^ self.modified_accounts_salt() + ) # Check if this pre-allocation has a group salt - group_salt = group_salt or self.group_salt() if group_salt: # Add custom salt to hash salt_hash = hashlib.sha256(group_salt.encode("utf-8")).digest() diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_prealloc_group.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_prealloc_group.py index 744d0a3f4c7..bf83f35fec4 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_prealloc_group.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_prealloc_group.py @@ -7,13 +7,16 @@ import pytest +from execution_testing.base_types import Address from execution_testing.fixtures import BaseFixture, PreAllocGroups from execution_testing.forks import Fork, Prague from execution_testing.specs.base import BaseTest -from execution_testing.test_types import Alloc, Environment +from execution_testing.test_types import Environment +from execution_testing.vm import Op from ...shared.pre_alloc import AllocFlags -from ..filler import compute_pre_alloc_group_hash, default_output_directory +from ..filler import default_output_directory +from ..pre_alloc import Alloc class MockTest(BaseTest): @@ -46,17 +49,45 @@ def get_genesis_environment(self) -> Environment: return self.genesis_environment.set_fork_requirements(self.fork) +def test_pre_alloc_group_same() -> None: + """Test that pre_alloc_group("separate") forces unique grouping.""" + # Create mock environment and pre-allocation + env = Environment() + pre_1 = Alloc(fork=Prague, flags=AllocFlags.NONE) + pre_2 = Alloc(fork=Prague, flags=AllocFlags.NONE) + + # Deploy different contracts and fund eoas with different amounts, + # should still result in the same group hash. + pre_1.deploy_contract(code=Op.STOP) + pre_2.deploy_contract(code=Op.INVALID) + + pre_1.fund_eoa(amount=0) + pre_2.fund_eoa(amount=1) + + # Create test without marker + hash1 = pre_1.compute_pre_alloc_group_hash( + fork=Prague, genesis_environment=env, group_salt=None + ) + hash2 = pre_1.compute_pre_alloc_group_hash( + fork=Prague, genesis_environment=env, group_salt=None + ) + + # Hashes should be equal + assert hash1 == hash2 + + def test_pre_alloc_group_separate() -> None: """Test that pre_alloc_group("separate") forces unique grouping.""" # Create mock environment and pre-allocation env = Environment() - pre = Alloc() + pre = Alloc(fork=Prague, flags=AllocFlags.NONE) fork = Prague # Create test without marker test1 = MockTest(pre=pre, genesis_environment=env, fork=fork) - hash1 = compute_pre_alloc_group_hash( - base_test=test1, alloc_flags=AllocFlags.NONE + genesis_env1 = test1.get_genesis_environment() + hash1 = pre.compute_pre_alloc_group_hash( + fork=fork, genesis_environment=genesis_env1, group_salt=None ) # Create test with "separate" marker @@ -70,8 +101,12 @@ def test_pre_alloc_group_separate() -> None: test2 = MockTest( pre=pre, genesis_environment=env, request=mock_request, fork=fork ) - hash2 = compute_pre_alloc_group_hash( - base_test=test2, alloc_flags=AllocFlags.NONE + genesis_env2 = test2.get_genesis_environment() + # For "separate" marker, use the node ID as the salt + hash2 = pre.compute_pre_alloc_group_hash( + fork=fork, + genesis_environment=genesis_env2, + group_salt=mock_request.node.nodeid, ) # Hashes should be different due to "separate" marker @@ -79,8 +114,9 @@ def test_pre_alloc_group_separate() -> None: # Create another test without marker - should match first test test3 = MockTest(pre=pre, genesis_environment=env, fork=fork) - hash3 = compute_pre_alloc_group_hash( - base_test=test3, alloc_flags=AllocFlags.NONE + genesis_env3 = test3.get_genesis_environment() + hash3 = pre.compute_pre_alloc_group_hash( + fork=fork, genesis_environment=genesis_env3, group_salt=None ) assert hash1 == hash3 @@ -89,7 +125,7 @@ def test_pre_alloc_group_separate() -> None: def test_pre_alloc_group_custom_salt() -> None: """Test that custom group names create consistent grouping.""" env = Environment() - pre = Alloc() + pre = Alloc(fork=Prague, flags=AllocFlags.NONE) fork = Prague # Create test with custom group "eip1234" @@ -103,8 +139,9 @@ def test_pre_alloc_group_custom_salt() -> None: test1 = MockTest( pre=pre, genesis_environment=env, request=mock_request1, fork=fork ) - hash1 = compute_pre_alloc_group_hash( - base_test=test1, alloc_flags=AllocFlags.NONE + genesis_env1 = test1.get_genesis_environment() + hash1 = pre.compute_pre_alloc_group_hash( + fork=fork, genesis_environment=genesis_env1, group_salt="eip1234" ) # Create another test with same custom group "eip1234" @@ -120,8 +157,9 @@ def test_pre_alloc_group_custom_salt() -> None: test2 = MockTest( pre=pre, genesis_environment=env, request=mock_request2, fork=fork ) - hash2 = compute_pre_alloc_group_hash( - base_test=test2, alloc_flags=AllocFlags.NONE + genesis_env2 = test2.get_genesis_environment() + hash2 = pre.compute_pre_alloc_group_hash( + fork=fork, genesis_environment=genesis_env2, group_salt="eip1234" ) # Hashes should be the same - both in "eip1234" group @@ -138,8 +176,9 @@ def test_pre_alloc_group_custom_salt() -> None: test3 = MockTest( pre=pre, genesis_environment=env, request=mock_request3, fork=fork ) - hash3 = compute_pre_alloc_group_hash( - base_test=test3, alloc_flags=AllocFlags.NONE + genesis_env3 = test3.get_genesis_environment() + hash3 = pre.compute_pre_alloc_group_hash( + fork=fork, genesis_environment=genesis_env3, group_salt="eip5678" ) # Hash should be different - different custom group @@ -150,7 +189,7 @@ def test_pre_alloc_group_custom_salt() -> None: def test_pre_alloc_group_separate_different_nodeids() -> None: """Test that different tests with "separate" get different hashes.""" env = Environment() - pre = Alloc() + pre = Alloc(fork=Prague, flags=AllocFlags.NONE) fork = Prague # Create test with "separate" and nodeid1 @@ -164,8 +203,11 @@ def test_pre_alloc_group_separate_different_nodeids() -> None: test1 = MockTest( pre=pre, genesis_environment=env, request=mock_request1, fork=fork ) - hash1 = compute_pre_alloc_group_hash( - base_test=test1, alloc_flags=AllocFlags.NONE + genesis_env1 = test1.get_genesis_environment() + hash1 = pre.compute_pre_alloc_group_hash( + fork=fork, + genesis_environment=genesis_env1, + group_salt=mock_request1.node.nodeid, ) # Create test with "separate" and nodeid2 @@ -179,8 +221,11 @@ def test_pre_alloc_group_separate_different_nodeids() -> None: test2 = MockTest( pre=pre, genesis_environment=env, request=mock_request2, fork=fork ) - hash2 = compute_pre_alloc_group_hash( - base_test=test2, alloc_flags=AllocFlags.NONE + genesis_env2 = test2.get_genesis_environment() + hash2 = pre.compute_pre_alloc_group_hash( + fork=fork, + genesis_environment=genesis_env2, + group_salt=mock_request2.node.nodeid, ) # Hashes should be different due to different nodeids @@ -190,7 +235,7 @@ def test_pre_alloc_group_separate_different_nodeids() -> None: def test_no_pre_alloc_group_marker() -> None: """Test normal grouping without pre_alloc_group marker.""" env = Environment() - pre = Alloc() + pre = Alloc(fork=Prague, flags=AllocFlags.NONE) fork = Prague # Create test without marker but with request object @@ -202,14 +247,16 @@ def test_no_pre_alloc_group_marker() -> None: test1 = MockTest( pre=pre, genesis_environment=env, request=mock_request, fork=fork ) - hash1 = compute_pre_alloc_group_hash( - base_test=test1, alloc_flags=AllocFlags.NONE + genesis_env1 = test1.get_genesis_environment() + hash1 = pre.compute_pre_alloc_group_hash( + fork=fork, genesis_environment=genesis_env1, group_salt=None ) # Create test without any request test2 = MockTest(pre=pre, genesis_environment=env, fork=fork) - hash2 = compute_pre_alloc_group_hash( - base_test=test2, alloc_flags=AllocFlags.NONE + genesis_env2 = test2.get_genesis_environment() + hash2 = pre.compute_pre_alloc_group_hash( + fork=fork, genesis_environment=genesis_env2, group_salt=None ) # Hashes should be the same - both have no marker @@ -219,7 +266,7 @@ def test_no_pre_alloc_group_marker() -> None: def test_pre_alloc_group_with_reason() -> None: """Test that reason kwarg is accepted but doesn't affect grouping.""" env = Environment() - pre = Alloc() + pre = Alloc(fork=Prague, flags=AllocFlags.NONE) fork = Prague # Create test with custom group and reason @@ -236,8 +283,11 @@ def test_pre_alloc_group_with_reason() -> None: test1 = MockTest( pre=pre, genesis_environment=env, request=mock_request1, fork=fork ) - hash1 = compute_pre_alloc_group_hash( - base_test=test1, alloc_flags=AllocFlags.NONE + genesis_env1 = test1.get_genesis_environment() + hash1 = pre.compute_pre_alloc_group_hash( + fork=fork, + genesis_environment=genesis_env1, + group_salt="hardcoded_addresses", ) # Create another test with same group but different reason @@ -252,14 +302,107 @@ def test_pre_alloc_group_with_reason() -> None: test2 = MockTest( pre=pre, genesis_environment=env, request=mock_request2, fork=fork ) - hash2 = compute_pre_alloc_group_hash( - base_test=test2, alloc_flags=AllocFlags.NONE + genesis_env2 = test2.get_genesis_environment() + hash2 = pre.compute_pre_alloc_group_hash( + fork=fork, + genesis_environment=genesis_env2, + group_salt="hardcoded_addresses", ) # Hashes should be the same - reason doesn't affect grouping assert hash1 == hash2 +def test_pre_alloc_group_with_modified_alloc() -> None: + """Test that modifications to Alloc affect grouping via group_salt().""" + env = Environment() + fork = Prague + + # Create unmodified pre-allocation + pre1 = Alloc(fork=fork, flags=AllocFlags.NONE) + test1 = MockTest(pre=pre1, genesis_environment=env, fork=fork) + genesis_env1 = test1.get_genesis_environment() + hash1 = pre1.compute_pre_alloc_group_hash( + fork=fork, genesis_environment=genesis_env1, group_salt=None + ) + + # Create pre-allocation with a funded address + pre2 = Alloc(fork=fork, flags=AllocFlags.ALLOW_FUND_ADDRESS) + pre2.fund_address( + Address("0x1234567890123456789012345678901234567890"), amount=100 + ) + test2 = MockTest(pre=pre2, genesis_environment=env, fork=fork) + genesis_env2 = test2.get_genesis_environment() + hash2 = pre2.compute_pre_alloc_group_hash( + fork=fork, genesis_environment=genesis_env2, group_salt=None + ) + + # Hashes should be different - pre2 has modifications + assert hash1 != hash2 + + +def test_pre_alloc_explicit_salt_overrides_group_salt() -> None: + """Test that explicit group_salt parameter overrides group_salt() method.""" + env = Environment() + fork = Prague + + # Create pre-allocation with modifications + pre = Alloc(fork=fork, flags=AllocFlags.ALLOW_FUND_ADDRESS) + pre.fund_address( + Address("0x1234567890123456789012345678901234567890"), amount=100 + ) + + test1 = MockTest(pre=pre, genesis_environment=env, fork=fork) + genesis_env1 = test1.get_genesis_environment() + + # Hash with explicit salt should ignore the internal group_salt() + hash1 = pre.compute_pre_alloc_group_hash( + fork=fork, genesis_environment=genesis_env1, group_salt="custom_salt" + ) + + # Hash without explicit salt uses internal group_salt() + hash2 = pre.compute_pre_alloc_group_hash( + fork=fork, genesis_environment=genesis_env1, group_salt=None + ) + + # Hashes should be different + assert hash1 != hash2 + + +def test_pre_alloc_group_same_modifications() -> None: + """Test that identical modifications produce the same hash.""" + env = Environment() + fork = Prague + + # Create two pre-allocations with same modification + pre1 = Alloc(fork=fork, flags=AllocFlags.ALLOW_FUND_ADDRESS) + pre1.fund_address( + Address("0x1234567890123456789012345678901234567890"), amount=100 + ) + + pre2 = Alloc(fork=fork, flags=AllocFlags.ALLOW_FUND_ADDRESS) + pre2.fund_address( + Address("0x1234567890123456789012345678901234567890"), amount=100 + ) + + test1 = MockTest(pre=pre1, genesis_environment=env, fork=fork) + test2 = MockTest(pre=pre2, genesis_environment=env, fork=fork) + + genesis_env1 = test1.get_genesis_environment() + genesis_env2 = test2.get_genesis_environment() + + hash1 = pre1.compute_pre_alloc_group_hash( + fork=fork, genesis_environment=genesis_env1, group_salt=None + ) + + hash2 = pre2.compute_pre_alloc_group_hash( + fork=fork, genesis_environment=genesis_env2, group_salt=None + ) + + # Hashes should be the same - both have identical modifications + assert hash1 == hash2 + + class FormattedTest: """Represents a single formatted test.""" diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/pre_alloc.py index 0a7681c35fd..5d306fd2250 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/pre_alloc.py @@ -157,11 +157,14 @@ def __setitem__( ) -> None: """Set account associated with an address.""" self._flags.assert_allow_account_address_set() + if not isinstance(address, Address): + address = Address(address) + self._set_addresses.add(address) self.__internal_setitem__(address, account) def __internal_setitem__( self, - address: Address | FixedSizeBytesConvertible, + address: Address, account: Account | None, ) -> None: """ @@ -169,9 +172,6 @@ def __internal_setitem__( Called by the pre-alloc implementation to set an account. """ - if not isinstance(address, Address): - address = Address(address) - self._set_addresses.add(address) self.root[address] = account def __delitem__( @@ -179,20 +179,20 @@ def __delitem__( ) -> None: """Delete account associated with an address.""" self._flags.assert_allow_account_address_set() + if not isinstance(address, Address): + address = Address(address) + self._deleted_addresses.add(address) self.__internal_delitem__(address) def __internal_delitem__( self, - address: Address | FixedSizeBytesConvertible, + address: Address, ) -> None: """ Delete account associated with an address. Called by the pre-alloc implementation to delete an account. """ - if not isinstance(address, Address): - address = Address(address) - self._deleted_addresses.add(address) self.root.pop(address, None) def deterministic_deploy_contract( From b41f07ebe745d06b73a50a2c43733a5ee52838ea Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Sat, 7 Feb 2026 21:22:32 +0000 Subject: [PATCH 13/22] refactor: Remove `pre_fund_address` marker --- docs/writing_tests/test_markers.md | 6 +--- .../plugins/filler/tests/test_pre_alloc.py | 36 ------------------- .../filler/tests/test_prealloc_group.py | 8 ++--- .../plugins/shared/execute_fill.py | 5 --- .../plugins/shared/pre_alloc.py | 11 ------ .../tools/utility/generators.py | 1 - .../test_block_access_lists.py | 1 - .../test_beacon_root_contract.py | 12 ++----- .../eip6780_selfdestruct/test_selfdestruct.py | 21 ++++------- .../test_withdrawal_requests_during_fork.py | 1 - .../test_consolidations_during_fork.py | 1 - .../test_eip150_selfdestruct.py | 12 ++----- 12 files changed, 15 insertions(+), 100 deletions(-) diff --git a/docs/writing_tests/test_markers.md b/docs/writing_tests/test_markers.md index d25566b5f23..2f74fa8ac53 100644 --- a/docs/writing_tests/test_markers.md +++ b/docs/writing_tests/test_markers.md @@ -101,7 +101,7 @@ def type_4_default_transaction(sender: Account, pre: Alloc): @pytest.mark.with_all_typed_transactions @pytest.mark.valid_from("Prague") def test_something_with_all_tx_types( - state_test: StateTestFiller, + state_test: StateTestFiller, pre: Alloc, typed_transaction: Transaction ): @@ -336,10 +336,6 @@ Examples of this include: - Modifying the pre-alloc to have a balance of 2^256 - 1. - Address collisions that would require hash collisions. -### `@pytest.mark.pre_fund_address` - -This marker is used to mark tests that use the `pre.fund_eoa()` method to create and fund EOAs dynamically. - ### `@pytest.mark.pre_eoa_with_code` This marker is used to mark tests that create EOAs with code, which is not possible in a real-world scenario but may be useful for testing edge cases. diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_pre_alloc.py index fe1bec74fa1..59b9d3a879a 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_pre_alloc.py @@ -142,28 +142,6 @@ def test_alloc_fund_eoa_basic() -> None: assert account_2.balance == 2 * 10**18 -def test_alloc_fund_address() -> None: - """Test `Alloc.fund_address` functionality.""" - pre = create_test_alloc(flags=AllocFlags.ALLOW_FUND_ADDRESS) - address = Address(0x1234567890123456789012345678901234567890) - amount = 5 * 10**18 - - pre.fund_address(address, amount) - - assert address in pre - account = pre[address] - assert account is not None - assert account.balance == amount - - # Without flag: should raise - pre_without_flag = create_test_alloc() - with pytest.raises( - ValueError, - match="Cannot use pre.fund_address without proper marker", - ): - pre_without_flag.fund_address(address, amount) - - def test_alloc_empty_account() -> None: """Test `Alloc.empty_account` functionality.""" pre = create_test_alloc() @@ -259,20 +237,6 @@ def test_alloc_flag_allow_zero_nonce_contracts() -> None: pre_without_flag.deploy_contract(Op.STOP, nonce=0) -def test_alloc_flag_allow_fund_address() -> None: - """Test ALLOW_FUND_ADDRESS flag.""" - pre_with_flag = create_test_alloc(flags=AllocFlags.ALLOW_FUND_ADDRESS) - address = Address(0x9999999999999999999999999999999999999999) - amount = 5 * 10**18 - - # Should work for new address (not in pre yet) - pre_with_flag.fund_address(address, amount) - assert address in pre_with_flag - account = pre_with_flag[address] - assert account is not None - assert account.balance == amount - - def test_alloc_mutable_flag_combines_permissions() -> None: """Test that MUTABLE flag includes multiple permissions.""" pre = create_test_alloc(flags=AllocFlags.MUTABLE) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_prealloc_group.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_prealloc_group.py index bf83f35fec4..1e6a6a6149f 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_prealloc_group.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_prealloc_group.py @@ -327,7 +327,7 @@ def test_pre_alloc_group_with_modified_alloc() -> None: ) # Create pre-allocation with a funded address - pre2 = Alloc(fork=fork, flags=AllocFlags.ALLOW_FUND_ADDRESS) + pre2 = Alloc(fork=fork, flags=AllocFlags.NONE) pre2.fund_address( Address("0x1234567890123456789012345678901234567890"), amount=100 ) @@ -347,7 +347,7 @@ def test_pre_alloc_explicit_salt_overrides_group_salt() -> None: fork = Prague # Create pre-allocation with modifications - pre = Alloc(fork=fork, flags=AllocFlags.ALLOW_FUND_ADDRESS) + pre = Alloc(fork=fork, flags=AllocFlags.NONE) pre.fund_address( Address("0x1234567890123456789012345678901234567890"), amount=100 ) @@ -375,12 +375,12 @@ def test_pre_alloc_group_same_modifications() -> None: fork = Prague # Create two pre-allocations with same modification - pre1 = Alloc(fork=fork, flags=AllocFlags.ALLOW_FUND_ADDRESS) + pre1 = Alloc(fork=fork, flags=AllocFlags.NONE) pre1.fund_address( Address("0x1234567890123456789012345678901234567890"), amount=100 ) - pre2 = Alloc(fork=fork, flags=AllocFlags.ALLOW_FUND_ADDRESS) + pre2 = Alloc(fork=fork, flags=AllocFlags.NONE) pre2.fund_address( Address("0x1234567890123456789012345678901234567890"), amount=100 ) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/execute_fill.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/execute_fill.py index 88fe039d8da..724ca1206ba 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/execute_fill.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/execute_fill.py @@ -52,11 +52,6 @@ class AllocFlagsMarker: ), flag=AllocFlags.MUTABLE, ), - AllocFlagsMarker( - name="pre_fund_address", - description="Marks a test to so it can use the `pre.fund_eoa` method.", - flag=AllocFlags.ALLOW_FUND_ADDRESS, - ), AllocFlagsMarker( name="pre_eoa_with_code", description="Marks a test to allow creating EOAs with code.", diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/pre_alloc.py index 5d306fd2250..6ff5e7ff934 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/pre_alloc.py @@ -32,7 +32,6 @@ class AllocFlags(IntFlag): ALLOW_ZERO_NONCE_CONTRACTS = auto() ALLOW_EOA_WITH_CODE = auto() ALLOW_EOA_WITH_HARDCODED_NONCE = auto() - ALLOW_FUND_ADDRESS = auto() MUTABLE = ( ALLOW_ADDRESS_SET_TO_ACCOUNT @@ -83,15 +82,6 @@ def assert_allow_zero_nonce_contracts(self) -> None: ) return - def assert_allow_fund_address(self) -> None: - """Raise an exception if the ALLOW_FUND_ADDRESS flag is not set.""" - if AllocFlags.ALLOW_FUND_ADDRESS not in self: - raise ValueError( - "Cannot use pre.fund_address without proper marker. " - "Use `pytest.mark.pre_fund_address` to allow this." - ) - return - def assert_allow_eoa_with_code(self) -> None: """Raise an exception if the ALLOW_EOA_WITH_CODE flag is not set.""" if AllocFlags.ALLOW_EOA_WITH_CODE not in self: @@ -369,7 +359,6 @@ def fund_address( insufficient """ - self._flags.assert_allow_fund_address() self._pre_funded_addresses.add(address) return self._fund_address( address=address, diff --git a/packages/testing/src/execution_testing/tools/utility/generators.py b/packages/testing/src/execution_testing/tools/utility/generators.py index d35182f1e0b..948980a490c 100644 --- a/packages/testing/src/execution_testing/tools/utility/generators.py +++ b/packages/testing/src/execution_testing/tools/utility/generators.py @@ -180,7 +180,6 @@ def decorator(func: SystemContractDeployTestFunction) -> Callable: ids=lambda x: x.name.lower(), ) @pytest.mark.pre_address_set_to_account - @pytest.mark.pre_fund_address @pytest.mark.valid_at_transition_to(fork.name()) def wrapper( blockchain_test: BlockchainTestFiller, diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py index 8657ab5e3f7..57540276be3 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py @@ -2194,7 +2194,6 @@ def test_bal_cross_tx_storage_revert_to_zero( ) -@pytest.mark.pre_fund_address() def test_bal_cross_block_ripemd160_state_leak( pre: Alloc, blockchain_test: BlockchainTestFiller, diff --git a/tests/cancun/eip4788_beacon_root/test_beacon_root_contract.py b/tests/cancun/eip4788_beacon_root/test_beacon_root_contract.py index 96bdeffdd33..445122f9cf9 100644 --- a/tests/cancun/eip4788_beacon_root/test_beacon_root_contract.py +++ b/tests/cancun/eip4788_beacon_root/test_beacon_root_contract.py @@ -125,16 +125,8 @@ def test_beacon_root_contract_calls( "system_address_balance", [ pytest.param(0, id="empty_system_address"), - pytest.param( - 1, - id="one_wei_system_address", - marks=pytest.mark.pre_fund_address(), - ), - pytest.param( - int(1e18), - id="one_eth_system_address", - marks=pytest.mark.pre_fund_address(), - ), + pytest.param(1, id="one_wei_system_address"), + pytest.param(int(1e18), id="one_eth_system_address"), ], ) @pytest.mark.valid_from("Cancun") diff --git a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py index 62a24b7c5eb..e18ebc3f5c0 100644 --- a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py +++ b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py @@ -190,10 +190,7 @@ def selfdestruct_code( ) @pytest.mark.parametrize( "selfdestruct_contract_initial_balance", - [ - 0, - pytest.param(100_000, marks=pytest.mark.pre_fund_address()), - ], + [0, 100_000], ) @pytest.mark.valid_from("Shanghai") def test_create_selfdestruct_same_tx( @@ -366,7 +363,7 @@ def test_create_selfdestruct_same_tx( @pytest.mark.parametrize("call_times", [0, 1]) @pytest.mark.parametrize( "selfdestruct_contract_initial_balance", - [0, pytest.param(100_000, marks=pytest.mark.pre_fund_address())], + [0, 100_000], ) @pytest.mark.valid_from("Shanghai") def test_self_destructing_initcode( @@ -498,7 +495,7 @@ def test_self_destructing_initcode( @pytest.mark.parametrize("tx_value", [0, 100_000]) @pytest.mark.parametrize( "selfdestruct_contract_initial_balance", - [0, pytest.param(100_000, marks=pytest.mark.pre_fund_address())], + [0, 100_000], ) @pytest.mark.valid_from("Shanghai") def test_self_destructing_initcode_create_tx( @@ -565,7 +562,7 @@ def test_self_destructing_initcode_create_tx( ) @pytest.mark.parametrize( "selfdestruct_contract_initial_balance", - [0, pytest.param(100_000, marks=pytest.mark.pre_fund_address())], + [0, 100_000], ) @pytest.mark.parametrize("recreate_times", [1]) @pytest.mark.parametrize("call_times", [1]) @@ -983,10 +980,7 @@ def test_selfdestruct_created_same_block_different_tx( @pytest.mark.parametrize("call_times", [1]) -@pytest.mark.parametrize( - "selfdestruct_contract_initial_balance", - [0, pytest.param(1, marks=pytest.mark.pre_fund_address())], -) +@pytest.mark.parametrize("selfdestruct_contract_initial_balance", [0, 1]) @pytest.mark.parametrize("call_opcode", [Op.DELEGATECALL, Op.CALLCODE]) @pytest.mark.parametrize("create_opcode", [Op.CREATE]) @pytest.mark.valid_from("Shanghai") @@ -1287,10 +1281,7 @@ def test_calling_from_pre_existing_contract_to_new_contract( @pytest.mark.parametrize("create_opcode", [Op.CREATE, Op.CREATE2]) -@pytest.mark.parametrize( - "selfdestruct_contract_initial_balance", - [0, pytest.param(100_000, marks=pytest.mark.pre_fund_address())], -) +@pytest.mark.parametrize("selfdestruct_contract_initial_balance", [0, 100_000]) @pytest.mark.parametrize( "call_times,sendall_recipient_addresses", [ diff --git a/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests_during_fork.py b/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests_during_fork.py index 0fe76103bb3..9f0949a0b6d 100644 --- a/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests_during_fork.py +++ b/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests_during_fork.py @@ -82,7 +82,6 @@ ) @pytest.mark.parametrize("timestamp", [15_000 - BLOCKS_BEFORE_FORK], ids=[""]) @pytest.mark.pre_address_set_to_account -@pytest.mark.pre_fund_address def test_withdrawal_requests_during_fork( blockchain_test: BlockchainTestFiller, blocks: List[Block], diff --git a/tests/prague/eip7251_consolidations/test_consolidations_during_fork.py b/tests/prague/eip7251_consolidations/test_consolidations_during_fork.py index 3133afcbf4d..1c09b8578f5 100644 --- a/tests/prague/eip7251_consolidations/test_consolidations_during_fork.py +++ b/tests/prague/eip7251_consolidations/test_consolidations_during_fork.py @@ -82,7 +82,6 @@ ) @pytest.mark.parametrize("timestamp", [15_000 - BLOCKS_BEFORE_FORK], ids=[""]) @pytest.mark.pre_address_set_to_account -@pytest.mark.pre_fund_address def test_consolidation_requests_during_fork( blockchain_test: BlockchainTestFiller, blocks: List[Block], diff --git a/tests/tangerine_whistle/eip150_operation_gas_costs/test_eip150_selfdestruct.py b/tests/tangerine_whistle/eip150_operation_gas_costs/test_eip150_selfdestruct.py index 8d329e466ec..9ea26259a34 100644 --- a/tests/tangerine_whistle/eip150_operation_gas_costs/test_eip150_selfdestruct.py +++ b/tests/tangerine_whistle/eip150_operation_gas_costs/test_eip150_selfdestruct.py @@ -577,11 +577,7 @@ def test_selfdestruct_state_access_boundary( "beneficiary_initial_balance", [ pytest.param(0, id="dead_beneficiary"), - pytest.param( - 1, - id="alive_beneficiary", - marks=pytest.mark.pre_fund_address(), - ), + pytest.param(1, id="alive_beneficiary"), ], ) @pytest.mark.valid_from("TangerineWhistle") @@ -690,11 +686,7 @@ def test_selfdestruct_to_precompile( "beneficiary_initial_balance", [ pytest.param(0, id="dead_beneficiary"), - pytest.param( - 1, - id="alive_beneficiary", - marks=pytest.mark.pre_fund_address(), - ), + pytest.param(1, id="alive_beneficiary"), ], ) @pytest.mark.valid_from("TangerineWhistle") From fd1bae35354dfd2a2f25c07ba5fcd3d4f2724eb6 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Sat, 7 Feb 2026 21:46:00 +0000 Subject: [PATCH 14/22] refactor: Remove extra flags --- docs/writing_tests/test_markers.md | 24 +--- .../plugins/filler/tests/test_pre_alloc.py | 31 ++--- .../plugins/shared/execute_fill.py | 67 ++--------- .../plugins/shared/pre_alloc.py | 111 ++++-------------- .../tools/utility/generators.py | 2 +- .../test_block_access_lists.py | 2 +- .../test_block_access_lists_opcodes.py | 2 +- .../test_beacon_root_contract.py | 4 +- ..._dynamic_create2_selfdestruct_collision.py | 4 +- tests/frontier/validation/test_transaction.py | 2 +- .../test_initcollision.py | 2 +- .../test_revert_in_create.py | 2 +- .../test_modified_contract.py | 2 +- .../test_modified_withdrawal_contract.py | 2 +- .../test_withdrawal_requests_during_fork.py | 2 +- .../test_consolidations_during_fork.py | 2 +- .../test_modified_consolidation_contract.py | 2 +- tests/prague/eip7702_set_code_tx/test_gas.py | 8 +- .../eip7702_set_code_tx/test_set_code_txs.py | 8 +- 19 files changed, 66 insertions(+), 213 deletions(-) diff --git a/docs/writing_tests/test_markers.md b/docs/writing_tests/test_markers.md index 2f74fa8ac53..4569ba2a625 100644 --- a/docs/writing_tests/test_markers.md +++ b/docs/writing_tests/test_markers.md @@ -335,26 +335,10 @@ Examples of this include: - Modifying the pre-alloc to have a balance of 2^256 - 1. - Address collisions that would require hash collisions. - -### `@pytest.mark.pre_eoa_with_code` - -This marker is used to mark tests that create EOAs with code, which is not possible in a real-world scenario but may be useful for testing edge cases. - -### `@pytest.mark.pre_eoa_with_hardcoded_nonce` - -This marker is used to mark tests that create EOAs with a hardcoded nonce value, rather than the default nonce of 0. - -### `@pytest.mark.pre_zero_nonce_contracts` - -This marker is used to mark tests that deploy contracts with a nonce of zero, which is not possible in a real-world scenario. - -### `@pytest.mark.pre_deploy_to_hardcoded_address` - -This marker is used to mark tests that deploy contracts to a hardcoded address, rather than using the standard address derivation from the sender and nonce. - -### `@pytest.mark.pre_address_set_to_account` - -This marker is used to mark tests that set an address to an account directly in the pre-allocation. +- EOA accounts containing code +- EOA accounts with a hard-coded nonce +- Contracts having zero-nonce +- Deploying a contract to a hard-coded address ### `@pytest.mark.skip()` diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_pre_alloc.py index 59b9d3a879a..9bcd3f134a8 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_pre_alloc.py @@ -180,11 +180,12 @@ def test_alloc_flags(flags: AllocFlags) -> None: def test_alloc_flag_allow_account_address_set() -> None: - """Test ALLOW_ADDRESS_SET_TO_ACCOUNT flag.""" + """ + Test that setting a hard-coded address to an account only works + when mutable. + """ # With flag: should allow setting accounts directly - pre_with_flag = create_test_alloc( - flags=AllocFlags.ALLOW_ADDRESS_SET_TO_ACCOUNT - ) + pre_with_flag = create_test_alloc(flags=AllocFlags.MUTABLE) address = Address(0x1234567890123456789012345678901234567890) pre_with_flag[address] = Account(balance=100) @@ -192,18 +193,14 @@ def test_alloc_flag_allow_account_address_set() -> None: # Without flag: should raise pre_without_flag = create_test_alloc(flags=AllocFlags.NONE) - with pytest.raises( - ValueError, match="Cannot set an account to an address" - ): + with pytest.raises(ValueError, match="Cannot set items in immutable mode"): pre_without_flag[address] = Account(balance=100) def test_alloc_flag_allow_deploy_to_hardcoded_address() -> None: - """Test ALLOW_DEPLOY_TO_HARDCODED_ADDRESS flag.""" + """Test that deploying to hardcoded addresses requires MUTABLE flag.""" # With flag: should allow deploying to hardcoded address - pre_with_flag = create_test_alloc( - flags=AllocFlags.ALLOW_DEPLOY_TO_HARDCODED_ADDRESS - ) + pre_with_flag = create_test_alloc(flags=AllocFlags.MUTABLE) hardcoded_address = Address(0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF) contract = pre_with_flag.deploy_contract( Op.STOP, address=hardcoded_address @@ -213,16 +210,14 @@ def test_alloc_flag_allow_deploy_to_hardcoded_address() -> None: # Without flag: should raise pre_without_flag = create_test_alloc(flags=AllocFlags.NONE) - with pytest.raises(ValueError, match="Cannot deploy to hardcoded address"): + with pytest.raises(ValueError, match="Cannot set items in immutable mode"): pre_without_flag.deploy_contract(Op.STOP, address=hardcoded_address) def test_alloc_flag_allow_zero_nonce_contracts() -> None: - """Test ALLOW_ZERO_NONCE_CONTRACTS flag.""" + """Test that deploying contracts with zero nonce requires MUTABLE flag.""" # With flag: should allow deploying contracts with nonce 0 - pre_with_flag = create_test_alloc( - flags=AllocFlags.ALLOW_ZERO_NONCE_CONTRACTS - ) + pre_with_flag = create_test_alloc(flags=AllocFlags.MUTABLE) contract = pre_with_flag.deploy_contract(Op.STOP, nonce=0) assert contract in pre_with_flag account = pre_with_flag[contract] @@ -231,9 +226,7 @@ def test_alloc_flag_allow_zero_nonce_contracts() -> None: # Without flag: should raise pre_without_flag = create_test_alloc(flags=AllocFlags.NONE) - with pytest.raises( - ValueError, match="Cannot deploy contracts with zero nonce" - ): + with pytest.raises(ValueError, match="Cannot set items in immutable mode"): pre_without_flag.deploy_contract(Op.STOP, nonce=0) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/execute_fill.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/execute_fill.py index 724ca1206ba..bd572fae289 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/execute_fill.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/execute_fill.py @@ -2,7 +2,6 @@ Shared pytest fixtures and hooks for EEST generation modes (fill and execute). """ -from dataclasses import dataclass from typing import List import pytest @@ -35,57 +34,6 @@ """ -@dataclass -class AllocFlagsMarker: - """Marker for allocation flags.""" - - name: str - description: str - flag: AllocFlags - - -ALLOC_FLAGS_MARKERS = [ - AllocFlagsMarker( - name="pre_alloc_mutable", - description=( - "Marks a test to allow impossible mutations in the pre-state." - ), - flag=AllocFlags.MUTABLE, - ), - AllocFlagsMarker( - name="pre_eoa_with_code", - description="Marks a test to allow creating EOAs with code.", - flag=AllocFlags.ALLOW_EOA_WITH_CODE, - ), - AllocFlagsMarker( - name="pre_eoa_with_hardcoded_nonce", - description=( - "Marks a test to allow creating EOAs with a hardcoded nonce." - ), - flag=AllocFlags.ALLOW_EOA_WITH_HARDCODED_NONCE, - ), - AllocFlagsMarker( - name="pre_zero_nonce_contracts", - description=( - "Marks a test to allow deploying contracts with zero nonce." - ), - flag=AllocFlags.ALLOW_ZERO_NONCE_CONTRACTS, - ), - AllocFlagsMarker( - name="pre_deploy_to_hardcoded_address", - description=( - "Marks a test to allow deploying to a hardcoded address." - ), - flag=AllocFlags.ALLOW_DEPLOY_TO_HARDCODED_ADDRESS, - ), - AllocFlagsMarker( - name="pre_address_set_to_account", - description="Marks a test to allow setting an address to an account.", - flag=AllocFlags.ALLOW_ADDRESS_SET_TO_ACCOUNT, - ), -] - - @pytest.hookimpl(tryfirst=True) def pytest_configure(config: pytest.Config) -> None: """ @@ -231,11 +179,11 @@ def pytest_configure(config: pytest.Config) -> None: "markers", "fully_tagged: Marks a static test as fully tagged with all metadata.", ) - for flags in ALLOC_FLAGS_MARKERS: - config.addinivalue_line( - "markers", - f"{flags.name}: {flags.description}", - ) + config.addinivalue_line( + "markers", + "pre_alloc_mutable: Marks a test to allow impossible mutations in the " + "pre-state.", + ) @pytest.fixture(scope="function") @@ -329,9 +277,8 @@ def alloc_flags_from_test_markers( ) -> AllocFlags: """Return allocation mode for a given test based on its markers.""" flags = AllocFlags.NONE - for flag in ALLOC_FLAGS_MARKERS: - if request.node.get_closest_marker(flag.name): - flags |= flag.flag + if request.node.get_closest_marker("pre_alloc_mutable"): + flags |= AllocFlags.MUTABLE return flags diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/pre_alloc.py index 6ff5e7ff934..960ca7c81b0 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/pre_alloc.py @@ -27,91 +27,7 @@ class AllocFlags(IntFlag): """Feature flags for allocation behavior.""" NONE = 0 - ALLOW_ADDRESS_SET_TO_ACCOUNT = auto() - ALLOW_DEPLOY_TO_HARDCODED_ADDRESS = auto() - ALLOW_ZERO_NONCE_CONTRACTS = auto() - ALLOW_EOA_WITH_CODE = auto() - ALLOW_EOA_WITH_HARDCODED_NONCE = auto() - - MUTABLE = ( - ALLOW_ADDRESS_SET_TO_ACCOUNT - | ALLOW_DEPLOY_TO_HARDCODED_ADDRESS - | ALLOW_ZERO_NONCE_CONTRACTS - | ALLOW_EOA_WITH_CODE - | ALLOW_EOA_WITH_HARDCODED_NONCE - ) - - def is_mutable(self) -> bool: - """Return whether the pre-alloc is mutable.""" - return bool(self & AllocFlags.MUTABLE) - - def assert_allow_account_address_set(self) -> None: - """ - Raise an exception if the ALLOW_ADDRESS_SET_TO_ACCOUNT flag is not set. - """ - if AllocFlags.ALLOW_ADDRESS_SET_TO_ACCOUNT not in self: - raise ValueError( - "Cannot set an account to an address (pre[a] = b) without " - "proper marker. " - "Use `pytest.mark.pre_address_set_to_account` to allow this." - ) - return - - def assert_allow_deploy_to_hardcoded_address(self) -> None: - """ - Raise an exception if the ALLOW_DEPLOY_TO_HARDCODED_ADDRESS flag is - not set. - """ - if AllocFlags.ALLOW_DEPLOY_TO_HARDCODED_ADDRESS not in self: - raise ValueError( - "Cannot deploy to hardcoded address without proper marker. " - "Use `pytest.mark.pre_deploy_to_hardcoded_address` to allow " - "this." - ) - return - - def assert_allow_zero_nonce_contracts(self) -> None: - """ - Raise an exception if the ALLOW_ZERO_NONCE_CONTRACTS flag is not set. - """ - if AllocFlags.ALLOW_ZERO_NONCE_CONTRACTS not in self: - raise ValueError( - "Cannot deploy contracts with zero nonce without proper " - "marker. " - "Use `pytest.mark.pre_zero_nonce_contracts` to allow this." - ) - return - - def assert_allow_eoa_with_code(self) -> None: - """Raise an exception if the ALLOW_EOA_WITH_CODE flag is not set.""" - if AllocFlags.ALLOW_EOA_WITH_CODE not in self: - raise ValueError( - "Cannot create EOAs with code without proper marker. " - "Use `pytest.mark.pre_eoa_with_code` to allow this." - ) - return - - def assert_allow_eoa_with_hardcoded_nonce(self) -> None: - """ - Raise an exception if the ALLOW_EOA_WITH_HARDCODED_NONCE flag is not - set. - """ - if AllocFlags.ALLOW_EOA_WITH_HARDCODED_NONCE not in self: - raise ValueError( - "Cannot create EOAs with a hardcoded nonce without proper " - "marker. " - "Use `pytest.mark.pre_eoa_with_hardcoded_nonce` to allow this." - ) - return - - def assert_mutable(self) -> None: - """Raises an exception if the MUTABLE flag is not set.""" - if self.is_mutable(): - raise ValueError( - "Cannot set items in immutable mode. " - "Use `pytest.mark.pre_alloc_mutable` to allow mutable mode." - ) - return + MUTABLE = auto() class Alloc(BaseAlloc): @@ -128,6 +44,19 @@ class Alloc(BaseAlloc): default_factory=set ) + def is_mutable(self) -> bool: + """Return whether the pre-alloc is mutable.""" + return bool(self._flags & AllocFlags.MUTABLE) + + def assert_mutable(self) -> None: + """Raises an exception if the MUTABLE flag is not set.""" + if not self.is_mutable(): + raise ValueError( + "Cannot set items in immutable mode. " + "Use `pytest.mark.pre_alloc_mutable` to allow mutable mode." + ) + return + def __init__( self, *args: Any, @@ -146,7 +75,7 @@ def __setitem__( account: Account | None, ) -> None: """Set account associated with an address.""" - self._flags.assert_allow_account_address_set() + self.assert_mutable() if not isinstance(address, Address): address = Address(address) self._set_addresses.add(address) @@ -168,7 +97,7 @@ def __delitem__( self, address: Address | FixedSizeBytesConvertible ) -> None: """Delete account associated with an address.""" - self._flags.assert_allow_account_address_set() + self.assert_mutable() if not isinstance(address, Address): address = Address(address) self._deleted_addresses.add(address) @@ -257,11 +186,11 @@ def deploy_contract( removed in the future! """ if address is not None: - self._flags.assert_allow_deploy_to_hardcoded_address() + self.assert_mutable() self._hardcoded_addresses_deployed_to.add(Address(address)) if Number(nonce) == 0: - self._flags.assert_allow_zero_nonce_contracts() + self.assert_mutable() return self._deploy_contract( code=code, @@ -308,10 +237,10 @@ def fund_eoa( unique EOA will be returned. """ if code is not None: - self._flags.assert_allow_eoa_with_code() + self.assert_mutable() if nonce is not None: - self._flags.assert_allow_eoa_with_hardcoded_nonce() + self.assert_mutable() return self._fund_eoa( amount=amount, diff --git a/packages/testing/src/execution_testing/tools/utility/generators.py b/packages/testing/src/execution_testing/tools/utility/generators.py index 948980a490c..38cabd79cda 100644 --- a/packages/testing/src/execution_testing/tools/utility/generators.py +++ b/packages/testing/src/execution_testing/tools/utility/generators.py @@ -179,7 +179,7 @@ def decorator(func: SystemContractDeployTestFunction) -> Callable: ], ids=lambda x: x.name.lower(), ) - @pytest.mark.pre_address_set_to_account + @pytest.mark.pre_alloc_mutable @pytest.mark.valid_at_transition_to(fork.name()) def wrapper( blockchain_test: BlockchainTestFiller, diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py index 57540276be3..5f1080f55d0 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py @@ -2542,7 +2542,7 @@ def test_bal_all_transaction_types( ) -@pytest.mark.pre_address_set_to_account() +@pytest.mark.pre_alloc_mutable() def test_bal_lexicographic_address_ordering( pre: Alloc, blockchain_test: BlockchainTestFiller, diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py index 39537f96754..2276b24ba09 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py @@ -2434,7 +2434,7 @@ def test_bal_create_selfdestruct_to_self_with_call( ) -@pytest.mark.pre_address_set_to_account() +@pytest.mark.pre_alloc_mutable() def test_bal_create2_collision( pre: Alloc, blockchain_test: BlockchainTestFiller, diff --git a/tests/cancun/eip4788_beacon_root/test_beacon_root_contract.py b/tests/cancun/eip4788_beacon_root/test_beacon_root_contract.py index 445122f9cf9..d1ee37f5fde 100644 --- a/tests/cancun/eip4788_beacon_root/test_beacon_root_contract.py +++ b/tests/cancun/eip4788_beacon_root/test_beacon_root_contract.py @@ -670,7 +670,7 @@ def test_beacon_root_transition( @pytest.mark.parametrize("timestamp", [15_000]) @pytest.mark.valid_at_transition_to("Cancun") -@pytest.mark.pre_address_set_to_account() +@pytest.mark.pre_alloc_mutable() def test_no_beacon_root_contract_at_transition( blockchain_test: BlockchainTestFiller, pre: Alloc, @@ -749,7 +749,7 @@ def test_no_beacon_root_contract_at_transition( ], ) @pytest.mark.valid_at_transition_to("Cancun") -@pytest.mark.pre_address_set_to_account() +@pytest.mark.pre_alloc_mutable() def test_beacon_root_contract_deploy( blockchain_test: BlockchainTestFiller, pre: Alloc, diff --git a/tests/cancun/eip6780_selfdestruct/test_dynamic_create2_selfdestruct_collision.py b/tests/cancun/eip6780_selfdestruct/test_dynamic_create2_selfdestruct_collision.py index 6f830bdf073..1dd3f72bd68 100644 --- a/tests/cancun/eip6780_selfdestruct/test_dynamic_create2_selfdestruct_collision.py +++ b/tests/cancun/eip6780_selfdestruct/test_dynamic_create2_selfdestruct_collision.py @@ -31,7 +31,7 @@ @pytest.mark.parametrize( "create2_dest_already_in_state", ( - pytest.param(True, marks=pytest.mark.pre_address_set_to_account), + pytest.param(True, marks=pytest.mark.pre_alloc_mutable), False, ), ) @@ -261,7 +261,7 @@ def test_dynamic_create2_selfdestruct_collision( @pytest.mark.parametrize( "create2_dest_already_in_state", ( - pytest.param(True, marks=pytest.mark.pre_address_set_to_account), + pytest.param(True, marks=pytest.mark.pre_alloc_mutable), False, ), ) diff --git a/tests/frontier/validation/test_transaction.py b/tests/frontier/validation/test_transaction.py index 315c4c5a6ba..2f10ce2bfed 100644 --- a/tests/frontier/validation/test_transaction.py +++ b/tests/frontier/validation/test_transaction.py @@ -64,7 +64,7 @@ def test_tx_gas_limit( ), ], ) -@pytest.mark.pre_eoa_with_hardcoded_nonce +@pytest.mark.pre_alloc_mutable def test_tx_nonce( blockchain_test: BlockchainTestFiller, pre: Alloc, diff --git a/tests/paris/eip7610_create_collision/test_initcollision.py b/tests/paris/eip7610_create_collision/test_initcollision.py index aba13b83411..bc1b2f60813 100644 --- a/tests/paris/eip7610_create_collision/test_initcollision.py +++ b/tests/paris/eip7610_create_collision/test_initcollision.py @@ -56,7 +56,7 @@ ], ), # We need to modify the pre-alloc to include the collision - pytest.mark.pre_address_set_to_account, + pytest.mark.pre_alloc_mutable, ] diff --git a/tests/paris/eip7610_create_collision/test_revert_in_create.py b/tests/paris/eip7610_create_collision/test_revert_in_create.py index 4bd731d454f..695a251897f 100644 --- a/tests/paris/eip7610_create_collision/test_revert_in_create.py +++ b/tests/paris/eip7610_create_collision/test_revert_in_create.py @@ -20,7 +20,7 @@ pytestmark = [ pytest.mark.valid_from("Paris"), # We need to modify the pre-alloc to include the collision - pytest.mark.pre_address_set_to_account, + pytest.mark.pre_alloc_mutable, ] diff --git a/tests/prague/eip6110_deposits/test_modified_contract.py b/tests/prague/eip6110_deposits/test_modified_contract.py index 54802151ed7..be34749ea1a 100644 --- a/tests/prague/eip6110_deposits/test_modified_contract.py +++ b/tests/prague/eip6110_deposits/test_modified_contract.py @@ -22,7 +22,7 @@ pytestmark = [ pytest.mark.valid_from("Prague"), - pytest.mark.pre_address_set_to_account(), + pytest.mark.pre_alloc_mutable(), ] REFERENCE_SPEC_GIT_PATH = ref_spec_6110.git_path diff --git a/tests/prague/eip7002_el_triggerable_withdrawals/test_modified_withdrawal_contract.py b/tests/prague/eip7002_el_triggerable_withdrawals/test_modified_withdrawal_contract.py index 6af07533127..adce6a08e0b 100644 --- a/tests/prague/eip7002_el_triggerable_withdrawals/test_modified_withdrawal_contract.py +++ b/tests/prague/eip7002_el_triggerable_withdrawals/test_modified_withdrawal_contract.py @@ -31,7 +31,7 @@ pytestmark: List[pytest.MarkDecorator] = [ pytest.mark.valid_from("Prague"), - pytest.mark.pre_address_set_to_account(), + pytest.mark.pre_alloc_mutable(), ] diff --git a/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests_during_fork.py b/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests_during_fork.py index 9f0949a0b6d..2f8118690ed 100644 --- a/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests_during_fork.py +++ b/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests_during_fork.py @@ -81,7 +81,7 @@ ], ) @pytest.mark.parametrize("timestamp", [15_000 - BLOCKS_BEFORE_FORK], ids=[""]) -@pytest.mark.pre_address_set_to_account +@pytest.mark.pre_alloc_mutable def test_withdrawal_requests_during_fork( blockchain_test: BlockchainTestFiller, blocks: List[Block], diff --git a/tests/prague/eip7251_consolidations/test_consolidations_during_fork.py b/tests/prague/eip7251_consolidations/test_consolidations_during_fork.py index 1c09b8578f5..3a883580737 100644 --- a/tests/prague/eip7251_consolidations/test_consolidations_during_fork.py +++ b/tests/prague/eip7251_consolidations/test_consolidations_during_fork.py @@ -81,7 +81,7 @@ ], ) @pytest.mark.parametrize("timestamp", [15_000 - BLOCKS_BEFORE_FORK], ids=[""]) -@pytest.mark.pre_address_set_to_account +@pytest.mark.pre_alloc_mutable def test_consolidation_requests_during_fork( blockchain_test: BlockchainTestFiller, blocks: List[Block], diff --git a/tests/prague/eip7251_consolidations/test_modified_consolidation_contract.py b/tests/prague/eip7251_consolidations/test_modified_consolidation_contract.py index 319a015f098..fce87399467 100644 --- a/tests/prague/eip7251_consolidations/test_modified_consolidation_contract.py +++ b/tests/prague/eip7251_consolidations/test_modified_consolidation_contract.py @@ -31,7 +31,7 @@ pytestmark: List[pytest.MarkDecorator] = [ pytest.mark.valid_from("Prague"), - pytest.mark.pre_address_set_to_account(), + pytest.mark.pre_alloc_mutable(), ] diff --git a/tests/prague/eip7702_set_code_tx/test_gas.py b/tests/prague/eip7702_set_code_tx/test_gas.py index c78d688c0e6..eb694b93535 100644 --- a/tests/prague/eip7702_set_code_tx/test_gas.py +++ b/tests/prague/eip7702_set_code_tx/test_gas.py @@ -722,7 +722,7 @@ def gas_test_parameter_args( { "authority_type": AddressType.CONTRACT, }, - marks=[pytest.mark.pre_eoa_with_code], + marks=[pytest.mark.pre_alloc_mutable], id="single_valid_authorization_invalid_contract_authority", ), pytest.param( @@ -734,7 +734,7 @@ def gas_test_parameter_args( ], "authorizations_count": multiple_authorizations_count, }, - marks=[pytest.mark.pre_eoa_with_code], + marks=[pytest.mark.pre_alloc_mutable], id="multiple_authorizations_empty_account_then_contract_authority", ), pytest.param( @@ -743,7 +743,7 @@ def gas_test_parameter_args( "authority_type": [AddressType.EOA, AddressType.CONTRACT], "authorizations_count": multiple_authorizations_count, }, - marks=[pytest.mark.pre_eoa_with_code], + marks=[pytest.mark.pre_alloc_mutable], id="multiple_authorizations_eoa_then_contract_authority", ), pytest.param( @@ -753,7 +753,7 @@ def gas_test_parameter_args( "authority_type": [AddressType.EOA, AddressType.CONTRACT], "authorizations_count": multiple_authorizations_count, }, - marks=[pytest.mark.pre_eoa_with_code], + marks=[pytest.mark.pre_alloc_mutable], id="multiple_authorizations_eoa_self_sponsored_then_contract_authority", ), ] diff --git a/tests/prague/eip7702_set_code_tx/test_set_code_txs.py b/tests/prague/eip7702_set_code_tx/test_set_code_txs.py index 031cdb8f38b..e1b5ef365db 100644 --- a/tests/prague/eip7702_set_code_tx/test_set_code_txs.py +++ b/tests/prague/eip7702_set_code_tx/test_set_code_txs.py @@ -2622,7 +2622,7 @@ def test_valid_tx_invalid_chain_id( ), ], ) -@pytest.mark.pre_eoa_with_hardcoded_nonce() +@pytest.mark.pre_alloc_mutable() def test_nonce_validity( state_test: StateTestFiller, pre: Alloc, @@ -2694,7 +2694,7 @@ def test_nonce_validity( ) -@pytest.mark.pre_eoa_with_hardcoded_nonce() +@pytest.mark.pre_alloc_mutable() def test_nonce_overflow_after_first_authorization( state_test: StateTestFiller, pre: Alloc, @@ -3982,8 +3982,8 @@ def test_authorization_reusing_nonce( [True, False], ) @pytest.mark.exception_test -@pytest.mark.pre_eoa_with_code -@pytest.mark.pre_eoa_with_hardcoded_nonce +@pytest.mark.pre_alloc_mutable +@pytest.mark.pre_alloc_mutable def test_set_code_from_account_with_non_delegating_code( state_test: StateTestFiller, pre: Alloc, From ef40818bcf8cdc18e8052d35d13d2c098f8d22f3 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Sat, 7 Feb 2026 21:51:46 +0000 Subject: [PATCH 15/22] fix: execute --- .../plugins/execute/pre_alloc.py | 32 ++----------------- 1 file changed, 2 insertions(+), 30 deletions(-) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py index e07c6e96935..00e0a533f48 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py @@ -893,37 +893,9 @@ def alloc_flags( Otherwise skip. """ - if ( - AllocFlags.ALLOW_ADDRESS_SET_TO_ACCOUNT - in alloc_flags_from_test_markers - ): + if AllocFlags.MUTABLE in alloc_flags_from_test_markers: pytest.skip( - "Execute mode does not allow setting an address directly in the " - "pre-alloc." - ) - - if ( - AllocFlags.ALLOW_DEPLOY_TO_HARDCODED_ADDRESS - in alloc_flags_from_test_markers - ): - pytest.skip( - "Execute mode does not allow deploying to a hardcoded address." - ) - - if AllocFlags.ALLOW_ZERO_NONCE_CONTRACTS in alloc_flags_from_test_markers: - pytest.skip( - "Execute mode does not allow deploying contracts with zero nonce." - ) - - if AllocFlags.ALLOW_EOA_WITH_CODE in alloc_flags_from_test_markers: - pytest.skip("Execute mode does not allow creating EOAs with code.") - - if ( - AllocFlags.ALLOW_EOA_WITH_HARDCODED_NONCE - in alloc_flags_from_test_markers - ): - pytest.skip( - "Execute mode does not allow creating EOAs with a hardcoded nonce." + "Execute mode cannot run tests where the pre-alloction is mutated." ) return alloc_flags_from_test_markers From 7b24cbff96e53ae725ad4c127ed118bee2359be4 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Sun, 8 Feb 2026 22:41:32 +0000 Subject: [PATCH 16/22] refactor: Move helper methods --- .../plugins/filler/pre_alloc.py | 34 +++---------------- .../execution_testing/test_types/__init__.py | 4 +++ .../execution_testing/test_types/helpers.py | 22 ++++++++++++ 3 files changed, 31 insertions(+), 29 deletions(-) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py index 0bc77ddae9b..3edb5864bc5 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py @@ -33,6 +33,8 @@ EOA, Environment, compute_deterministic_create2_address, + contract_address_from_hash, + eoa_from_hash, ) from execution_testing.tools import Initcode @@ -55,30 +57,6 @@ def pytest_addoption(parser: pytest.Parser) -> None: EMPTY_ACCOUNT_HASH = Account().hash() -def contract_address_from_account(account_hash: Hash, salt: int) -> Address: - """ - Calculate a deterministic address for a contract given the properties of - the account. - - Useful to not duplicate accounts in the pre-allocation when grouping - many tests. - """ - return Address( - Bytes(account_hash + salt.to_bytes(64, "big")).sha256()[12:] - ) - - -def eoa_from_account(account_hash: Hash, salt: int) -> EOA: - """ - Calculate a deterministic EOA for a contract given the properties of - the account. - - Useful to not duplicate accounts in the pre-allocation when grouping - many tests. - """ - return EOA(key=Bytes(account_hash + salt.to_bytes(64, "big")).sha256()) - - class Alloc(SharedAlloc): """Allocation of accounts in the state, pre and post test execution.""" @@ -275,9 +253,7 @@ def _deploy_contract( else: account_hash = account.hash() salt = self.get_next_account_salt(account_hash) - contract_address = contract_address_from_account( - account_hash, salt - ) + contract_address = contract_address_from_hash(account_hash, salt) self.__internal_setitem__(contract_address, account) if label is None: @@ -368,7 +344,7 @@ def _fund_eoa( account_hash = account.hash() salt = self.get_next_account_salt(account_hash) - eoa = eoa_from_account(account_hash, salt) + eoa = eoa_from_hash(account_hash, salt) if account.nonce > 0: eoa.nonce = account.nonce @@ -403,7 +379,7 @@ def _empty_account(self) -> Address: Filler implementation of empty account creation. """ salt = self.get_next_account_salt(EMPTY_ACCOUNT_HASH) - return Address(eoa_from_account(EMPTY_ACCOUNT_HASH, salt)) + return Address(eoa_from_hash(EMPTY_ACCOUNT_HASH, salt)) def sha256_from_string(s: str) -> int: diff --git a/packages/testing/src/execution_testing/test_types/__init__.py b/packages/testing/src/execution_testing/test_types/__init__.py index c87148f92a8..979a76d49ba 100644 --- a/packages/testing/src/execution_testing/test_types/__init__.py +++ b/packages/testing/src/execution_testing/test_types/__init__.py @@ -28,6 +28,8 @@ compute_create2_address, compute_create_address, compute_deterministic_create2_address, + contract_address_from_hash, + eoa_from_hash, ) from .phase_manager import TestPhase, TestPhaseManager from .receipt_types import TransactionLog, TransactionReceipt @@ -88,5 +90,7 @@ "compute_create_address", "compute_create2_address", "compute_deterministic_create2_address", + "contract_address_from_hash", + "eoa_from_hash", "keccak256", ) diff --git a/packages/testing/src/execution_testing/test_types/helpers.py b/packages/testing/src/execution_testing/test_types/helpers.py index 71511411682..2d6dc73087b 100644 --- a/packages/testing/src/execution_testing/test_types/helpers.py +++ b/packages/testing/src/execution_testing/test_types/helpers.py @@ -143,6 +143,28 @@ def add_kzg_version( return kzg_versioned_hashes +def contract_address_from_hash(account_hash: Hash, salt: int) -> Address: + """ + Calculate an address from a given (account) hash plus a salt. + + Useful to not duplicate accounts in the pre-allocation when grouping + many tests. + """ + return Address( + Bytes(account_hash + salt.to_bytes(64, "big")).sha256()[12:] + ) + + +def eoa_from_hash(account_hash: Hash, salt: int) -> EOA: + """ + Calculate an EOA from a given (account) hash plus a salt. + + Useful to not duplicate accounts in the pre-allocation when grouping + many tests. + """ + return EOA(key=Bytes(account_hash + salt.to_bytes(64, "big")).sha256()) + + class TestParameterGroup(BaseModel): """ Base class for grouping test parameters in a dataclass. Provides a generic From 9abd171dc4b6237913c23ef469400dcb38902401 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Mon, 9 Feb 2026 01:03:34 +0000 Subject: [PATCH 17/22] fix: Separators --- .../src/execution_testing/base_types/composite_types.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/testing/src/execution_testing/base_types/composite_types.py b/packages/testing/src/execution_testing/base_types/composite_types.py index cdc38fd8d0e..cdb6bd2769d 100644 --- a/packages/testing/src/execution_testing/base_types/composite_types.py +++ b/packages/testing/src/execution_testing/base_types/composite_types.py @@ -533,7 +533,11 @@ def __bool__(self: "Account") -> bool: def hash(self) -> Hash: """Return the hash of the account given its properties.""" data = self.model_dump(mode="json") - blob = json.dumps(data, sort_keys=True).encode("utf-8") + blob = json.dumps( + data, + sort_keys=True, + separators=(",", ":"), + ).encode("utf-8") return Hash(hashlib.sha256(blob).digest()) @classmethod From 8d8fe16fbd5d7a1dd6a7662e3ebd53b4b93556f3 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Mon, 9 Feb 2026 01:09:18 +0000 Subject: [PATCH 18/22] fix: Static tests --- .../specs/static_state/account.py | 99 +++++++------------ .../specs/static_state/state_static.py | 20 +--- 2 files changed, 40 insertions(+), 79 deletions(-) diff --git a/packages/testing/src/execution_testing/specs/static_state/account.py b/packages/testing/src/execution_testing/specs/static_state/account.py index fe5c12aa11d..ac257a80730 100644 --- a/packages/testing/src/execution_testing/specs/static_state/account.py +++ b/packages/testing/src/execution_testing/specs/static_state/account.py @@ -1,16 +1,22 @@ """Account structure of ethereum/tests fillers.""" +import hashlib +import json from typing import Any, Dict, List, Mapping, Set, Tuple from pydantic import BaseModel, ConfigDict from execution_testing.base_types import ( - Bytes, + Account, EthereumTestRootModel, + Hash, HexNumber, - Storage, ) -from execution_testing.test_types import Alloc +from execution_testing.test_types import ( + Alloc, + contract_address_from_hash, + eoa_from_hash, +) from .common import ( AddressOrTagInFiller, @@ -87,6 +93,17 @@ def resolve(self, tags: TagDict) -> Dict[str, Any]: account_properties["storage"] = resolved_storage return account_properties + def hash(self) -> Hash: + """Return a hash of the account as it is in the filler.""" + dumped = self.model_dump(mode="json", exclude_none=True) + return hashlib.sha256( + json.dumps( + dumped, + sort_keys=True, + separators=(",", ":"), + ).encode("utf-8") + ).digest() + class PreInFiller(EthereumTestRootModel): """Class that represents a pre-state in filler.""" @@ -174,20 +191,21 @@ def setup(self, pre: Alloc, all_dependencies: Dict[str, Tag]) -> TagDict: # Step 4: Pre-deploy all contract tags and pre-fund EOAs to get # addresses + account_salts: Dict[Hash, int] = {} for tag_name in resolution_order: if tag_name in tag_to_address: tag = tag_to_address[tag_name] + account_hash = self.root[tag].hash() + salt = account_salts.get(account_hash, 0) + account_salts[account_hash] = salt + 1 if isinstance(tag, ContractTag): - # Deploy with placeholder to get address - deployed_address = pre.deploy_contract( - code=b"", # Temporary placeholder - label=tag_name, + # Get a placeholder address + resolved_accounts[tag_name] = contract_address_from_hash( + account_hash, salt ) - resolved_accounts[tag_name] = deployed_address elif isinstance(tag, SenderTag): - # Create EOA to get address - use amount=1 to ensure - # account is created - eoa = pre.fund_eoa(amount=1, label=tag_name) + # Create a placeholder EOA + eoa = eoa_from_hash(account_hash, salt) # Store the EOA object for SenderKeyTag resolution resolved_accounts[tag_name] = eoa @@ -203,62 +221,15 @@ def setup(self, pre: Alloc, all_dependencies: Dict[str, Tag]) -> TagDict: # All addresses are now available, so resolve properties account_properties = account.resolve(resolved_accounts) - if isinstance(tag, ContractTag): - # Update the already-deployed contract + if isinstance(tag, (ContractTag, SenderTag)): deployed_address = resolved_accounts[tag_name] - deployed_account = pre[deployed_address] - - if deployed_account is not None: - if "code" in account_properties: - deployed_account.code = Bytes( - account_properties["code"] - ) - if "balance" in account_properties: - deployed_account.balance = account_properties[ - "balance" - ] - if "nonce" in account_properties: - deployed_account.nonce = account_properties[ - "nonce" - ] - if "storage" in account_properties: - deployed_account.storage = Storage( - root=account_properties["storage"] - ) - - elif isinstance(tag, SenderTag): - eoa_account = pre[resolved_accounts[tag_name]] - - if eoa_account is not None: - if "balance" in account_properties: - eoa_account.balance = account_properties["balance"] - if "nonce" in account_properties: - eoa_account.nonce = account_properties["nonce"] - if "code" in account_properties: - eoa_account.code = Bytes( - account_properties["code"] - ) - if "storage" in account_properties: - eoa_account.storage = Storage( - root=account_properties["storage"] - ) + pre[deployed_address] = Account(**account_properties) # Step 6: Now process non-tagged accounts (including code compilation) - for address, account in non_tagged_to_process: - account_properties = account.resolve(resolved_accounts) - if "balance" in account_properties: - pre.fund_address(address, account_properties["balance"]) - - existing_account = pre[address] - if existing_account is not None: - if "code" in account_properties: - existing_account.code = Bytes(account_properties["code"]) - if "nonce" in account_properties: - existing_account.nonce = account_properties["nonce"] - if "storage" in account_properties: - existing_account.storage = Storage( - root=account_properties["storage"] - ) + for address, account_in_filler in non_tagged_to_process: + pre[address] = Account( + **account_in_filler.resolve(resolved_accounts) + ) # Step 7: Handle any extra dependencies not in pre for extra_dependency in all_dependencies: diff --git a/packages/testing/src/execution_testing/specs/static_state/state_static.py b/packages/testing/src/execution_testing/specs/static_state/state_static.py index cdfd2974531..37619835a64 100644 --- a/packages/testing/src/execution_testing/specs/static_state/state_static.py +++ b/packages/testing/src/execution_testing/specs/static_state/state_static.py @@ -206,14 +206,8 @@ def test_state_vectors( if self.info and self.info.pytest_marks: for mark in self.info.pytest_marks: - if mark == "pre_alloc_group": - test_state_vectors = pytest.mark.pre_alloc_group( - "separate", - reason="Requires separate pre-alloc grouping", - )(test_state_vectors) - else: - apply_mark = getattr(pytest.mark, mark) - test_state_vectors = apply_mark(test_state_vectors) + apply_mark = getattr(pytest.mark, mark) + test_state_vectors = apply_mark(test_state_vectors) if has_tags: test_state_vectors = pytest.mark.tagged(test_state_vectors) @@ -223,13 +217,9 @@ def test_state_vectors( ) else: test_state_vectors = pytest.mark.untagged(test_state_vectors) - test_state_vectors = pytest.mark.pre_alloc_group( - "separate", reason="Uses hard-coded addresses" - )(test_state_vectors) - if not fully_tagged: - test_state_vectors = pytest.mark.pre_alloc_mutable( - test_state_vectors - ) + + # All static tests are mutable since we do `pre[0x123...] = Account()` + test_state_vectors = pytest.mark.pre_alloc_mutable(test_state_vectors) return test_state_vectors From 9e773681060539a23e7201e0582eafbb8dde7344 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Mon, 9 Feb 2026 01:32:05 +0000 Subject: [PATCH 19/22] fix: Tox, review comments --- .../base_types/composite_types.py | 9 --------- .../cli/pytest_commands/plugins/filler/filler.py | 2 +- .../pytest_commands/plugins/filler/pre_alloc.py | 12 ++++++++++-- .../plugins/filler/tests/test_prealloc_group.py | 4 +++- .../pytest_commands/plugins/shared/pre_alloc.py | 6 ++++-- .../specs/static_state/account.py | 16 +++++++++------- .../test_types/account_types.py | 6 ++++-- 7 files changed, 31 insertions(+), 24 deletions(-) diff --git a/packages/testing/src/execution_testing/base_types/composite_types.py b/packages/testing/src/execution_testing/base_types/composite_types.py index cdb6bd2769d..05c05889b2d 100644 --- a/packages/testing/src/execution_testing/base_types/composite_types.py +++ b/packages/testing/src/execution_testing/base_types/composite_types.py @@ -348,15 +348,6 @@ def canary(self) -> "Storage": ) -class FrozenStorage(Storage): - """Frozen storage.""" - - model_config = { - **Storage.model_config, - "frozen": True, - } - - class Account(CamelModel): """State associated with an address.""" diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py index 5c260b5c114..eb7cca48a23 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py @@ -1492,6 +1492,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: # We got the marker but unspecified, pass test name group_salt = request.node.nodeid + pre_alloc_hash: str | None = None # Phase 1: Generate pre-allocation groups if session.phase_manager.is_pre_alloc_generation: # Use the original update_pre_alloc_groups method which @@ -1515,7 +1516,6 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: # Phase 2: Use pre-allocation groups (only for # BlockchainEngineXFixture) - pre_alloc_hash = None if ( FixtureFillingPhase.PRE_ALLOC_GENERATION in fixture_format.format_phases diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py index 3edb5864bc5..7f519fc084b 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py @@ -4,7 +4,7 @@ import inspect from functools import cache from hashlib import sha256 -from typing import Dict, List, Literal +from typing import Any, Dict, List, Literal import pytest from pydantic import PrivateAttr @@ -63,6 +63,12 @@ class Alloc(SharedAlloc): _eoa_fund_amount_default: int = PrivateAttr(10**21) _account_salt: Dict[Hash, int] = PrivateAttr(default_factory=dict) + def __init__( + self, *args: Any, fork: Fork, flags: AllocFlags, **kwargs: Any + ) -> None: + """Initialize the pre-alloc.""" + super().__init__(*args, fork=fork, flags=flags, **kwargs) + def get_next_account_salt(self, account_hash: Hash) -> int: """Retrieve the next salt for this account.""" salt = self._account_salt.get(account_hash, 0) @@ -100,7 +106,9 @@ def modified_accounts_salt(self) -> int: buffer += b"\0" for altered_account in sorted(altered_accounts): buffer += altered_account - buffer += self[altered_account].hash() + account = self[altered_account] + assert account is not None + buffer += account.hash() if self._deleted_addresses: buffer += b"\1" for deleted_address in sorted(self._deleted_addresses): diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_prealloc_group.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_prealloc_group.py index 1e6a6a6149f..e9f8ec66630 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_prealloc_group.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_prealloc_group.py @@ -342,7 +342,9 @@ def test_pre_alloc_group_with_modified_alloc() -> None: def test_pre_alloc_explicit_salt_overrides_group_salt() -> None: - """Test that explicit group_salt parameter overrides group_salt() method.""" + """ + Test that explicit group_salt parameter overrides group_salt() method. + """ env = Environment() fork = Prague diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/pre_alloc.py index 960ca7c81b0..4932e5f0386 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/pre_alloc.py @@ -277,8 +277,10 @@ def fund_address( """ 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. + Add a funded account to the pre-allocation. + The address must not already exist in the pre-allocation. To set the + balance of an account, use the `amount` parameter in `fund_eoa()` or + the `balance` parameter in `deploy_contract()` at creation time. Args: address: Address to fund diff --git a/packages/testing/src/execution_testing/specs/static_state/account.py b/packages/testing/src/execution_testing/specs/static_state/account.py index ac257a80730..84e2bbbcb52 100644 --- a/packages/testing/src/execution_testing/specs/static_state/account.py +++ b/packages/testing/src/execution_testing/specs/static_state/account.py @@ -96,13 +96,15 @@ def resolve(self, tags: TagDict) -> Dict[str, Any]: def hash(self) -> Hash: """Return a hash of the account as it is in the filler.""" dumped = self.model_dump(mode="json", exclude_none=True) - return hashlib.sha256( - json.dumps( - dumped, - sort_keys=True, - separators=(",", ":"), - ).encode("utf-8") - ).digest() + return Hash( + hashlib.sha256( + json.dumps( + dumped, + sort_keys=True, + separators=(",", ":"), + ).encode("utf-8") + ).digest() + ) class PreInFiller(EthereumTestRootModel): diff --git a/packages/testing/src/execution_testing/test_types/account_types.py b/packages/testing/src/execution_testing/test_types/account_types.py index e66fc463fe7..4cb216537ad 100644 --- a/packages/testing/src/execution_testing/test_types/account_types.py +++ b/packages/testing/src/execution_testing/test_types/account_types.py @@ -464,8 +464,10 @@ def fund_address( """ 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. + Add a funded account to the pre-allocation. + The address must not already exist in the pre-allocation. To set the + balance of an account, use the `amount` parameter in `fund_eoa()` or + the `balance` parameter in `deploy_contract()` at creation time. Args: address: Address to fund From b97e31b34c362985d505bd9eb744d09f5bea4b25 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Mon, 9 Feb 2026 13:43:22 +0000 Subject: [PATCH 20/22] fix(tests): Mark test_creates_collisions --- tests/benchmark/compute/instruction/test_system.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/benchmark/compute/instruction/test_system.py b/tests/benchmark/compute/instruction/test_system.py index 87fbdcf67f1..ad1af5a4d42 100644 --- a/tests/benchmark/compute/instruction/test_system.py +++ b/tests/benchmark/compute/instruction/test_system.py @@ -171,6 +171,7 @@ def test_create( Op.CREATE2, ], ) +@pytest.mark.pre_alloc_mutable def test_creates_collisions( benchmark_test: BenchmarkTestFiller, pre: Alloc, From efe377e5a926f27db257e45db3b24c52fa13c8aa Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Tue, 10 Feb 2026 00:20:29 +0100 Subject: [PATCH 21/22] Update tests/prague/eip7702_set_code_tx/test_set_code_txs.py Co-authored-by: felipe --- tests/prague/eip7702_set_code_tx/test_set_code_txs.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/prague/eip7702_set_code_tx/test_set_code_txs.py b/tests/prague/eip7702_set_code_tx/test_set_code_txs.py index e1b5ef365db..647b39ed8b3 100644 --- a/tests/prague/eip7702_set_code_tx/test_set_code_txs.py +++ b/tests/prague/eip7702_set_code_tx/test_set_code_txs.py @@ -3983,7 +3983,6 @@ def test_authorization_reusing_nonce( ) @pytest.mark.exception_test @pytest.mark.pre_alloc_mutable -@pytest.mark.pre_alloc_mutable def test_set_code_from_account_with_non_delegating_code( state_test: StateTestFiller, pre: Alloc, From 14dfa744f262e5f89bc2cf25cb49e6513c62331b Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Tue, 10 Feb 2026 19:40:34 +0000 Subject: [PATCH 22/22] fix: `pre.fund_address` --- .../plugins/execute/pre_alloc.py | 46 +++++-------------- .../plugins/filler/pre_alloc.py | 8 +--- .../plugins/shared/pre_alloc.py | 10 +++- 3 files changed, 21 insertions(+), 43 deletions(-) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py index 00e0a533f48..bf395045132 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py @@ -705,7 +705,7 @@ def _fund_eoa( def _fund_address( self, address: Address, - amount: NumberConvertible, + amount: int, *, minimum_balance: bool, ) -> None: @@ -713,27 +713,20 @@ def _fund_address( Execute implementation of address funding. """ current_balance = self._eth_rpc.get_balance(address) - fund_amount = int(Number(amount)) - if minimum_balance: - if current_balance >= fund_amount: + if current_balance >= amount: cur_eth = current_balance / 10**18 - min_eth = fund_amount / 10**18 + min_eth = amount / 10**18 logger.info( f"Skipping funding for address {address} " f"(label={address.label}): current balance " f"{cur_eth:.18f} ETH >= minimum {min_eth:.18f} ETH" ) - if address in self: - account = self[address] - if account is not None: - self[address] = account.copy(balance=current_balance) - else: - self.__internal_setitem__( - address, Account(balance=current_balance) - ) + self.__internal_setitem__( + address, Account(balance=current_balance) + ) return - fund_eth = fund_amount / 10**18 + fund_eth = amount / 10**18 logger.debug( f"Funding address to minimum balance {address} " f"(label={address.label}): {fund_eth:.18f} ETH" @@ -742,11 +735,11 @@ def _fund_address( action="fund_address", target=address.label, to=address, - value=fund_amount - current_balance, + value=amount - current_balance, ) - new_balance = fund_amount + new_balance = amount else: - fund_eth = fund_amount / 10**18 + fund_eth = amount / 10**18 logger.debug( f"Funding address {address} (label={address.label}): " f"{fund_eth:.18f} ETH" @@ -757,24 +750,9 @@ def _fund_address( to=address, value=amount, ) - new_balance = current_balance + fund_amount + new_balance = current_balance + amount - if address in self: - account = self[address] - if account is not None: - self[address] = account.copy(balance=new_balance) - cur_eth = current_balance / 10**18 - new_eth = new_balance / 10**18 - logger.debug( - f"Updated balance for existing address {address}: " - f"{cur_eth:.18f} ETH -> {new_eth:.18f} ETH" - ) - else: - self.__internal_setitem__( - address, Account(balance=new_balance) - ) - else: - self.__internal_setitem__(address, Account(balance=new_balance)) + self.__internal_setitem__(address, Account(balance=new_balance)) logger.info( f"Address {address} funding tx created (label={address.label}): " diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py index 7f519fc084b..75f6ee6e5d2 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py @@ -366,7 +366,7 @@ def _fund_eoa( def _fund_address( self, address: Address, - amount: NumberConvertible, + amount: int, *, minimum_balance: bool, ) -> None: @@ -374,12 +374,6 @@ def _fund_address( Filler implementation of address funding. """ del minimum_balance - if address in self: - raise Exception( - "Cannot fund an account already in state. " - "Use the appropriate `amount`, `balance` arguments " - "when creating the account." - ) self.__internal_setitem__(address, Account(balance=amount)) def _empty_account(self) -> Address: diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/pre_alloc.py index 4932e5f0386..09bb2b311c0 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/pre_alloc.py @@ -290,17 +290,23 @@ def fund_address( insufficient """ + if address in self: + raise Exception( + "Cannot fund an account already in state. " + "Use the appropriate `amount`, `balance` arguments " + "when creating the account." + ) self._pre_funded_addresses.add(address) return self._fund_address( address=address, - amount=amount, + amount=int(Number(amount)), minimum_balance=minimum_balance, ) def _fund_address( self, address: Address, - amount: NumberConvertible, + amount: int, *, minimum_balance: bool, ) -> None: