diff --git a/docs/writing_tests/test_markers.md b/docs/writing_tests/test_markers.md index 73cf74cf451..4569ba2a625 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 ): @@ -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. @@ -335,6 +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. +- 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/base_types/composite_types.py b/packages/testing/src/execution_testing/base_types/composite_types.py index 1db6e9f7b2e..05c05889b2d 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, @@ -367,6 +369,11 @@ class Account(CamelModel): state. """ + model_config = { + **CamelModel.model_config, + "frozen": True, + } + @dataclass(kw_only=True) class NonceMismatchError(Exception): """ @@ -514,6 +521,16 @@ 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, + separators=(",", ":"), + ).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/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 3ead061db10..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 @@ -20,11 +20,9 @@ Number, Storage, StorageRootType, - ZeroPaddedHexNumber, ) from execution_testing.base_types.conversions import ( BytesConvertible, - FixedSizeBytesConvertible, NumberConvertible, ) from execution_testing.forks import Fork @@ -40,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 SharedAlloc +from ..shared.pre_alloc import AllocFlags from .contracts import ( check_deterministic_factory_deployment, deploy_deterministic_factory_contract, @@ -226,10 +225,9 @@ 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.""" - _fork: Fork = PrivateAttr() _sender: EOA = PrivateAttr() _eth_rpc: EthRPC = PrivateAttr() _pending_txs: List[PendingTransaction] = PrivateAttr(default_factory=list) @@ -244,7 +242,6 @@ class Alloc(BaseAlloc): def __init__( self, *args: Any, - fork: Fork, sender: EOA, eth_rpc: EthRPC, eoa_iterator: Iterator[EOA], @@ -255,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 @@ -263,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 @@ -303,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() @@ -410,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, @@ -423,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" @@ -472,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, @@ -560,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, @@ -573,19 +559,20 @@ 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, - 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" eoa = next(self._eoa_iterator) eoa.label = label amount_str = ( @@ -702,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" @@ -715,41 +702,31 @@ def fund_eoa( ) return eoa - def fund_address( + def _fund_address( self, address: Address, - amount: NumberConvertible, + amount: int, *, - 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)) - 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: - account.balance = ZeroPaddedHexNumber(current_balance) - else: - super().__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" @@ -758,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" @@ -773,51 +750,23 @@ 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: - account.balance = ZeroPaddedHexNumber(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: - super().__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, @@ -913,9 +862,27 @@ 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.MUTABLE in alloc_flags_from_test_markers: + pytest.skip( + "Execute mode cannot run tests where the pre-alloction is mutated." + ) + + 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, @@ -941,6 +908,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/filler.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py index 241d651c2e9..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 @@ -30,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, ) @@ -81,6 +81,7 @@ 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. @@ -423,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. @@ -470,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: @@ -1478,24 +1479,52 @@ 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) + 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 + + 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 # 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 + 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 # Phase 2: Use pre-allocation groups (only for # BlockchainEngineXFixture) - pre_alloc_hash = None if ( FixtureFillingPhase.PRE_ALLOC_GENERATION in fixture_format.format_phases ): - pre_alloc_hash = self.compute_pre_alloc_group_hash() + 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 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 dd60772f9e7..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 @@ -1,11 +1,10 @@ """Pre-alloc specifically conditioned for test filling.""" +import hashlib import inspect -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,11 +19,9 @@ StorageRootType, TestPrivateKey, TestPrivateKey2, - ZeroPaddedHexNumber, ) from execution_testing.base_types.conversions import ( BytesConvertible, - FixedSizeBytesConvertible, NumberConvertible, ) from execution_testing.fixtures import LabeledFixtureFormat @@ -34,13 +31,15 @@ DETERMINISTIC_FACTORY_ADDRESS, DETERMINISTIC_FACTORY_BYTECODE, EOA, + Environment, compute_deterministic_create2_address, + contract_address_from_hash, + eoa_from_hash, ) -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 SharedAlloc +from ..shared.pre_alloc import AllocFlags def pytest_addoption(parser: pytest.Parser) -> None: @@ -50,97 +49,112 @@ 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" +EMPTY_ACCOUNT_HASH = Account().hash() -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) - _alloc_mode: AllocMode = PrivateAttr() - _contract_address_iterator: Iterator[Address] = PrivateAttr() - _eoa_iterator: Iterator[EOA] = PrivateAttr() - _fork: Fork = PrivateAttr() + _account_salt: Dict[Hash, int] = PrivateAttr(default_factory=dict) def __init__( - self, - *args: Any, - alloc_mode: AllocMode, - contract_address_iterator: Iterator[Address], - eoa_iterator: Iterator[EOA], - fork: Fork, - **kwargs: Any, + self, *args: Any, fork: Fork, flags: AllocFlags, **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._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) + """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) + 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 - def deterministic_deploy_contract( + def modified_accounts_salt(self) -> int: + """ + 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. + """ + if ( + not self._set_addresses + and not self._pre_funded_addresses + and not self._hardcoded_addresses_deployed_to + and not self._deleted_addresses + ): + return 0 + + # 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 + 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): + buffer += deleted_address + + return int.from_bytes( + hashlib.sha256(buffer).digest()[:8], byteorder="big" + ) + + 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) + ^ self.modified_accounts_salt() + ) + + # Check if this pre-allocation has a 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, *, 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) @@ -171,7 +185,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, @@ -180,7 +194,7 @@ def deterministic_deploy_contract( ), ) - super().__setitem__( + self.__internal_setitem__( contract_address, Account( nonce=1, @@ -205,44 +219,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" - ) - assert address not in self, ( - f"address {address} already in allocation" - ) - contract_address = address - else: - contract_address = next(self._contract_address_iterator) - - if self._alloc_mode == AllocMode.STRICT: - assert Number(nonce) >= 1, ( - "impossible to deploy contract with nonce lower than one" - ) - code = self.code_pre_processor(code) code_bytes = ( bytes(code) if not isinstance(code, (bytes, str)) else code @@ -252,15 +246,24 @@ 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_hash(account_hash, salt) + + self.__internal_setitem__(contract_address, account) if label is None: # Try to deduce the label from the code frame = inspect.currentframe() @@ -278,48 +281,59 @@ 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, - 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. """ del label - eoa = next(self._eoa_iterator) if amount is None: amount = self._eoa_fund_amount_default 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 storage is None and delegation is None: + 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 and code is None: nonce = Number(0 if nonce is None else nonce) account = Account( 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 + 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 + 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 @@ -330,86 +344,44 @@ 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 - super().__setitem__(eoa, account) + else: + account = Account() + + account_hash = account.hash() + salt = self.get_next_account_salt(account_hash) + eoa = eoa_from_hash(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: + self.__internal_setitem__(eoa, account) return eoa - def fund_address( + def _fund_address( self, address: Address, - amount: NumberConvertible, + amount: int, *, - 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. """ - 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 - super().__setitem__(address, Account(balance=amount)) + del minimum_balance + 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. """ - eoa = next(self._eoa_iterator) - - return Address(eoa) - - -@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) + salt = self.get_next_account_salt(EMPTY_ACCOUNT_HASH) + return Address(eoa_from_hash(EMPTY_ACCOUNT_HASH, salt)) def sha256_from_string(s: str) -> int: @@ -467,70 +439,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], + alloc_flags: AllocFlags, fork: Fork | None, request: pytest.FixtureRequest, ) -> Alloc: @@ -542,8 +459,6 @@ def pre( actual_fork = request.node.fork return Alloc( - alloc_mode=alloc_mode, - contract_address_iterator=contract_address_iterator, - eoa_iterator=eoa_iterator, + 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..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 @@ -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={}) + 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: @@ -127,20 +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() - 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 - - def test_alloc_empty_account() -> None: """Test `Alloc.empty_account` functionality.""" pre = create_test_alloc() @@ -166,42 +167,116 @@ 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 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.MUTABLE) + 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 items in immutable mode"): + pre_without_flag[address] = Account(balance=100) + + +def test_alloc_flag_allow_deploy_to_hardcoded_address() -> None: + """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.MUTABLE) + 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 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 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.MUTABLE) + 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 set items in immutable mode"): + pre_without_flag.deploy_contract(Op.STOP, nonce=0) + + +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 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: """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 +294,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 +304,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/filler/tests/test_prealloc_group.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_prealloc_group.py index 650f8fb2a28..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 @@ -7,12 +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 default_output_directory +from ..pre_alloc import Alloc class MockTest(BaseTest): @@ -45,16 +49,46 @@ 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 = test1.compute_pre_alloc_group_hash() + 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 mock_request = Mock() @@ -67,14 +101,23 @@ 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() + 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 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() + 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 @@ -82,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" @@ -96,7 +139,10 @@ 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() + 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" mock_request2 = Mock() @@ -111,7 +157,10 @@ 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() + 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 assert hash1 == hash2 @@ -127,7 +176,10 @@ 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() + 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 assert hash1 != hash3 @@ -137,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 @@ -151,7 +203,12 @@ 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() + 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 mock_request2 = Mock() @@ -164,7 +221,12 @@ 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() + 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 assert hash1 != hash2 @@ -173,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 @@ -185,11 +247,17 @@ 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() + 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 = test2.compute_pre_alloc_group_hash() + 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 assert hash1 == hash2 @@ -198,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 @@ -215,7 +283,12 @@ 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() + 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 mock_request2 = Mock() @@ -229,12 +302,109 @@ 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() + 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.NONE) + 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.NONE) + 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.NONE) + pre1.fund_address( + Address("0x1234567890123456789012345678901234567890"), amount=100 + ) + + pre2 = Alloc(fork=fork, flags=AllocFlags.NONE) + 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/execute_fill.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/execute_fill.py index b0594208381..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 @@ -15,6 +15,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 = { @@ -157,11 +158,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 +179,11 @@ def pytest_configure(config: pytest.Config) -> None: "markers", "fully_tagged: Marks a static test as fully tagged with all metadata.", ) + config.addinivalue_line( + "markers", + "pre_alloc_mutable: Marks a test to allow impossible mutations in the " + "pre-state.", + ) @pytest.fixture(scope="function") @@ -270,6 +271,30 @@ 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 + if request.node.get_closest_marker("pre_alloc_mutable"): + flags |= AllocFlags.MUTABLE + 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..09bb2b311c0 --- /dev/null +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/pre_alloc.py @@ -0,0 +1,335 @@ +"""Shared pre-alloc functionality.""" + +from enum import IntFlag, auto +from typing import Any, Literal, Set + +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 + MUTABLE = auto() + + +class Alloc(BaseAlloc): + """ + Allocation subclass that enforces rules set by the allocation flags. + """ + + _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 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, + 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.assert_mutable() + if not isinstance(address, Address): + address = Address(address) + self._set_addresses.add(address) + self.__internal_setitem__(address, account) + + def __internal_setitem__( + self, + address: Address, + account: Account | None, + ) -> None: + """ + Set account associated with an address. + + Called by the pre-alloc implementation to set an account. + """ + self.root[address] = account + + def __delitem__( + self, address: Address | FixedSizeBytesConvertible + ) -> None: + """Delete account associated with an address.""" + self.assert_mutable() + if not isinstance(address, Address): + address = Address(address) + self._deleted_addresses.add(address) + self.__internal_delitem__(address) + + def __internal_delitem__( + self, + address: Address, + ) -> None: + """ + Delete account associated with an address. + + Called by the pre-alloc implementation to delete an account. + """ + 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.assert_mutable() + self._hardcoded_addresses_deployed_to.add(Address(address)) + + if Number(nonce) == 0: + self.assert_mutable() + + 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.assert_mutable() + + if nonce is not None: + self.assert_mutable() + + 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. + + 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 + 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 + + """ + 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=int(Number(amount)), + minimum_balance=minimum_balance, + ) + + def _fund_address( + self, + address: Address, + amount: int, + *, + 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/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]] 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..84e2bbbcb52 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,19 @@ 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 Hash( + 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 +193,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 +223,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 91f7217811f..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_modify( - 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 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/account_types.py b/packages/testing/src/execution_testing/test_types/account_types.py index 04106c40059..4cb216537ad 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: @@ -463,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 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 diff --git a/packages/testing/src/execution_testing/tools/utility/generators.py b/packages/testing/src/execution_testing/tools/utility/generators.py index 8819dfe04ad..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.execute(pytest.mark.skip(reason="modifies pre-alloc")) + @pytest.mark.pre_alloc_mutable @pytest.mark.valid_at_transition_to(fork.name()) def wrapper( blockchain_test: BlockchainTestFiller, @@ -248,9 +248,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" 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..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 @@ -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", [ @@ -2198,10 +2194,6 @@ 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", -) def test_bal_cross_block_ripemd160_state_leak( pre: Alloc, blockchain_test: BlockchainTestFiller, @@ -2550,6 +2542,7 @@ def test_bal_all_transaction_types( ) +@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_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/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..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,6 +2434,7 @@ def test_bal_create_selfdestruct_to_self_with_call( ) +@pytest.mark.pre_alloc_mutable() def test_bal_create2_collision( pre: Alloc, blockchain_test: BlockchainTestFiller, 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, 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/cancun/eip4788_beacon_root/test_beacon_root_contract.py b/tests/cancun/eip4788_beacon_root/test_beacon_root_contract.py index 62fb48a364e..d1ee37f5fde 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", [ @@ -130,30 +124,9 @@ 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( - 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", - ), - ), - 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", - ), - ), + pytest.param(0, id="empty_system_address"), + pytest.param(1, id="one_wei_system_address"), + pytest.param(int(1e18), id="one_eth_system_address"), ], ) @pytest.mark.valid_from("Cancun") @@ -697,10 +670,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_alloc_mutable() def test_no_beacon_root_contract_at_transition( blockchain_test: BlockchainTestFiller, pre: Alloc, @@ -779,14 +749,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_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 aefc864e9bc..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,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_alloc_mutable), 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_alloc_mutable), False, ), ) diff --git a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py index 6d30363b3e9..e18ebc3f5c0 100644 --- a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py +++ b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py @@ -188,7 +188,10 @@ 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, 100_000], +) @pytest.mark.valid_from("Shanghai") def test_create_selfdestruct_same_tx( state_test: StateTestFiller, @@ -358,7 +361,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, 100_000], +) @pytest.mark.valid_from("Shanghai") def test_self_destructing_initcode( state_test: StateTestFiller, @@ -487,7 +493,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, 100_000], +) @pytest.mark.valid_from("Shanghai") def test_self_destructing_initcode_create_tx( state_test: StateTestFiller, @@ -515,9 +524,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 +560,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, 100_000], +) @pytest.mark.parametrize("recreate_times", [1]) @pytest.mark.parametrize("call_times", [1]) @pytest.mark.valid_from("Shanghai") @@ -626,9 +640,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 @@ -998,9 +1014,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) diff --git a/tests/frontier/validation/test_transaction.py b/tests/frontier/validation/test_transaction.py index 33fd69ff131..2f10ce2bfed 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_alloc_mutable def test_tx_nonce( 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/paris/eip7610_create_collision/test_initcollision.py b/tests/paris/eip7610_create_collision/test_initcollision.py index 8c25f64674d..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_alloc_modify, + 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 f8834ea81ec..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_alloc_modify, + pytest.mark.pre_alloc_mutable, ] 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/eip6110_deposits/test_modified_contract.py b/tests/prague/eip6110_deposits/test_modified_contract.py index 8ab56a6775a..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.execute(pytest.mark.skip(reason="modifies pre-alloc")), + pytest.mark.pre_alloc_mutable(), ] 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_contract_deployment.py b/tests/prague/eip7002_el_triggerable_withdrawals/test_contract_deployment.py index f7160a6ea68..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", @@ -49,7 +44,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/eip7002_el_triggerable_withdrawals/test_modified_withdrawal_contract.py b/tests/prague/eip7002_el_triggerable_withdrawals/test_modified_withdrawal_contract.py index 6e351bfd603..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 @@ -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_alloc_mutable(), +] 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/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..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,9 +81,7 @@ ], ) @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_alloc_mutable 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..3a883580737 100644 --- a/tests/prague/eip7251_consolidations/test_consolidations_during_fork.py +++ b/tests/prague/eip7251_consolidations/test_consolidations_during_fork.py @@ -81,10 +81,7 @@ ], ) @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_alloc_mutable 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 b1e14f8ef91..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", @@ -51,7 +46,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/eip7251_consolidations/test_modified_consolidation_contract.py b/tests/prague/eip7251_consolidations/test_modified_consolidation_contract.py index 4e31c166bf2..fce87399467 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_alloc_mutable(), +] 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/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, diff --git a/tests/prague/eip7702_set_code_tx/test_gas.py b/tests/prague/eip7702_set_code_tx/test_gas.py index 27cd2725efe..eb694b93535 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,7 @@ def gas_test_parameter_args( { "authority_type": AddressType.CONTRACT, }, - marks=pytest.mark.pre_alloc_modify, + marks=[pytest.mark.pre_alloc_mutable], id="single_valid_authorization_invalid_contract_authority", ), pytest.param( @@ -738,7 +734,7 @@ def gas_test_parameter_args( ], "authorizations_count": multiple_authorizations_count, }, - marks=pytest.mark.pre_alloc_modify, + marks=[pytest.mark.pre_alloc_mutable], id="multiple_authorizations_empty_account_then_contract_authority", ), pytest.param( @@ -747,7 +743,7 @@ 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_mutable], id="multiple_authorizations_eoa_then_contract_authority", ), pytest.param( @@ -757,7 +753,7 @@ 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_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 b52d383a54e..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 @@ -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_alloc_mutable() 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_alloc_mutable() def test_nonce_overflow_after_first_authorization( state_test: StateTestFiller, pre: Alloc, @@ -3094,8 +3082,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( @@ -3996,9 +3982,7 @@ 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_alloc_mutable def test_set_code_from_account_with_non_delegating_code( state_test: StateTestFiller, pre: Alloc, @@ -4013,7 +3997,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 @@ -4031,12 +4017,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, 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..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 @@ -576,20 +576,8 @@ 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( - 1, - id="alive_beneficiary", - marks=pytest.mark.pre_alloc_group( - "eip150_selfdestruct_precompile_alive" - ), - ), + pytest.param(0, id="dead_beneficiary"), + pytest.param(1, id="alive_beneficiary"), ], ) @pytest.mark.valid_from("TangerineWhistle") @@ -697,20 +685,8 @@ 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( - 1, - id="alive_beneficiary", - marks=pytest.mark.pre_alloc_group( - "eip150_selfdestruct_precompile_boundary_alive" - ), - ), + pytest.param(0, id="dead_beneficiary"), + pytest.param(1, id="alive_beneficiary"), ], ) @pytest.mark.valid_from("TangerineWhistle")