From d90d9f4e856c92b9024cca27d28616fa2e03bdfa Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Wed, 19 Aug 2026 16:17:13 +0000 Subject: [PATCH 1/2] feat(monad_next): adopt EIP-7997 deterministic factory predeploy MONAD_NEXT carries the factory in its pre-allocation, ordered after Amsterdam by succession, and releases as the monad_eip7997 feature. Co-Authored-By: Claude --- .github/configs/feature.yaml | 4 ++++ .../pytest_commands/plugins/forks/forks.py | 4 +++- .../src/execution_testing/forks/base_fork.py | 20 +++++++++++++--- .../execution_testing/forks/forks/forks.py | 23 ++++++++++++++----- .../conftest.py | 10 ++++++++ .../eip7708_eth_transfer_logs/conftest.py | 10 ++++++++ .../conftest.py | 10 ++++++++ tests/amsterdam/eip7843_slotnum/conftest.py | 10 ++++++++ .../conftest.py | 10 ++++++++ .../conftest.py | 7 ++++++ .../test_factory.py | 12 ++++++++-- .../eip8024_dupn_swapn_exchange/conftest.py | 10 ++++++++ .../conftest.py | 10 ++++++++ .../conftest.py | 10 ++++++++ .../eip8070_sparse_blobpool/conftest.py | 7 ++++++ .../eip8246_selfdestruct_no_burn/conftest.py | 10 ++++++++ .../conftest.py | 9 ++++++++ 17 files changed, 164 insertions(+), 12 deletions(-) create mode 100644 tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/conftest.py create mode 100644 tests/amsterdam/eip7708_eth_transfer_logs/conftest.py create mode 100644 tests/amsterdam/eip7778_block_gas_accounting_without_refunds/conftest.py create mode 100644 tests/amsterdam/eip7843_slotnum/conftest.py create mode 100644 tests/amsterdam/eip7928_block_level_access_lists/conftest.py create mode 100644 tests/amsterdam/eip8024_dupn_swapn_exchange/conftest.py create mode 100644 tests/amsterdam/eip8037_state_creation_gas_cost_increase/conftest.py create mode 100644 tests/amsterdam/eip8038_state_access_gas_cost_increase/conftest.py create mode 100644 tests/amsterdam/eip8246_selfdestruct_no_burn/conftest.py diff --git a/.github/configs/feature.yaml b/.github/configs/feature.yaml index 7fca18fd9ef..ceb478c56b3 100644 --- a/.github/configs/feature.yaml +++ b/.github/configs/feature.yaml @@ -11,3 +11,7 @@ monad_runloop: evm-type: eels # Like `monad`, but `--monad-runloop` and eestnet chain id `30143` fill-params: -m blockchain_test --from=MONAD_EIGHT --until=MONAD_TEN --chain-id=30143 --monad-runloop -k "not invalid_header" + +monad_eip7997: + evm-type: eels + fill-params: -m blockchain_test --fork=MONAD_NEXT --chain-id=143 -k "not invalid_header" diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/forks.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/forks.py index 6075481806c..29ef4dc86e3 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/forks.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/forks.py @@ -1265,7 +1265,9 @@ def _process_with_marker_args( "Missing fork argument with 'valid_at_transition_to' marker." ) - if len(forks) > 1: + # A single EIP argument expands to one fork per enabling fork, so + # the limit is on the arguments rather than on the resolved forks. + if len(fork_args) > 1: raise Exception( "Too many forks specified to 'valid_at_transition_to' marker." ) diff --git a/packages/testing/src/execution_testing/forks/base_fork.py b/packages/testing/src/execution_testing/forks/base_fork.py index f842878c42f..808d94b6045 100644 --- a/packages/testing/src/execution_testing/forks/base_fork.py +++ b/packages/testing/src/execution_testing/forks/base_fork.py @@ -333,12 +333,23 @@ def _maybe_transitioned(fork_cls: "BaseForkMeta") -> "BaseForkMeta": @staticmethod def _is_subclass_of(a: "BaseForkMeta", b: "BaseForkMeta") -> bool: """ - Check if `a` is a subclass of `b`, taking fork transitions into - account. + Check if `a` is a subclass of `b`, taking fork transitions and + declared succession into account. + + A fork can follow another fork it does not inherit from, which + places it after that fork (and after everything that fork comes + after) in the fork order without adopting its behavior. """ a = BaseForkMeta._maybe_transitioned(a) b = BaseForkMeta._maybe_transitioned(b) - return issubclass(a, b) + if issubclass(a, b): + return True + followed = getattr(a, "_follows", None) + while followed is not None: + if issubclass(followed, b): + return True + followed = followed._follows + return False def __gt__(cls, other: "BaseForkMeta") -> bool: """Compare if a fork is newer than some other fork (cls > other).""" @@ -381,6 +392,7 @@ class BaseFork(ForkOpcodeInterface, metaclass=BaseForkMeta): _fork_by_timestamp: ClassVar[bool] = False _blob_constants: ClassVar[Dict[str, int]] = {} _deployed: ClassVar[bool] = True + _follows: ClassVar[Optional[Type["BaseFork"]]] = None _enabled_eips: ClassVar[Set[int]] = set() _enabling_forks: ClassVar[Set[Type["BaseFork"]]] = set() @@ -396,6 +408,7 @@ def __init_subclass__( transition_tool_name: Optional[str] = None, ignore: bool = False, bpo_fork: bool = False, + follows: Optional[Type["BaseFork"]] = None, ruleset_name: Optional[str] = None, fork_by_timestamp: Optional[bool] = None, deployed: Optional[bool] = None, @@ -410,6 +423,7 @@ def __init_subclass__( forks. """ cls._transition_tool_name = transition_tool_name + cls._follows = follows cls._ignore = ignore cls._bpo_fork = bpo_fork cls._ruleset_name = ruleset_name diff --git a/packages/testing/src/execution_testing/forks/forks/forks.py b/packages/testing/src/execution_testing/forks/forks/forks.py index ecf3b02f3ae..55e96539cd7 100644 --- a/packages/testing/src/execution_testing/forks/forks/forks.py +++ b/packages/testing/src/execution_testing/forks/forks/forks.py @@ -1805,12 +1805,6 @@ def _calculate_sstore_gas_mip8( return gas_cost -class MONAD_NEXT(MONAD_TEN): # noqa: N801 - """MONAD_NEXT fork, a placeholder identical to MONAD_TEN.""" - - pass - - class BPO1( Osaka, bpo_fork=True, @@ -1904,3 +1898,20 @@ def engine_payload_attribute_target_gas_limit(cls) -> bool: limit. """ return True + + +class MONAD_NEXT( # noqa: N801 + eips.EIP7997, + MONAD_TEN, + follows=Amsterdam, +): + """ + MONAD_NEXT fork. + + Amsterdam-based successor to MONAD_TEN, adopting the EIP-7997 + changes. The Amsterdam changes it does not adopt stay out of the + fork by not being inherited at all; the fork order still places + MONAD_NEXT after Amsterdam through `follows`. + """ + + pass diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/conftest.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/conftest.py new file mode 100644 index 00000000000..de52075d396 --- /dev/null +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/conftest.py @@ -0,0 +1,10 @@ +"""Pytest (plugin) definitions local to EIP-2780 tests.""" + +import pytest + + +def pytest_generate_tests(metafunc: pytest.Metafunc) -> None: + """Mark all tests in this subdir as not valid for Monad forks.""" + metafunc.definition.add_marker( + pytest.mark.not_valid_for("MONAD_EIGHT", subsequent_forks=True) + ) diff --git a/tests/amsterdam/eip7708_eth_transfer_logs/conftest.py b/tests/amsterdam/eip7708_eth_transfer_logs/conftest.py new file mode 100644 index 00000000000..fb1d9465b3e --- /dev/null +++ b/tests/amsterdam/eip7708_eth_transfer_logs/conftest.py @@ -0,0 +1,10 @@ +"""Pytest (plugin) definitions local to EIP-7708 tests.""" + +import pytest + + +def pytest_generate_tests(metafunc: pytest.Metafunc) -> None: + """Mark all tests in this subdir as not valid for Monad forks.""" + metafunc.definition.add_marker( + pytest.mark.not_valid_for("MONAD_EIGHT", subsequent_forks=True) + ) diff --git a/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/conftest.py b/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/conftest.py new file mode 100644 index 00000000000..5cbdebdc9ef --- /dev/null +++ b/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/conftest.py @@ -0,0 +1,10 @@ +"""Pytest (plugin) definitions local to EIP-7778 tests.""" + +import pytest + + +def pytest_generate_tests(metafunc: pytest.Metafunc) -> None: + """Mark all tests in this subdir as not valid for Monad forks.""" + metafunc.definition.add_marker( + pytest.mark.not_valid_for("MONAD_EIGHT", subsequent_forks=True) + ) diff --git a/tests/amsterdam/eip7843_slotnum/conftest.py b/tests/amsterdam/eip7843_slotnum/conftest.py new file mode 100644 index 00000000000..6e43f18fb35 --- /dev/null +++ b/tests/amsterdam/eip7843_slotnum/conftest.py @@ -0,0 +1,10 @@ +"""Pytest (plugin) definitions local to EIP-7843 tests.""" + +import pytest + + +def pytest_generate_tests(metafunc: pytest.Metafunc) -> None: + """Mark all tests in this subdir as not valid for Monad forks.""" + metafunc.definition.add_marker( + pytest.mark.not_valid_for("MONAD_EIGHT", subsequent_forks=True) + ) diff --git a/tests/amsterdam/eip7928_block_level_access_lists/conftest.py b/tests/amsterdam/eip7928_block_level_access_lists/conftest.py new file mode 100644 index 00000000000..3bedcfe2651 --- /dev/null +++ b/tests/amsterdam/eip7928_block_level_access_lists/conftest.py @@ -0,0 +1,10 @@ +"""Pytest (plugin) definitions local to EIP-7928 tests.""" + +import pytest + + +def pytest_generate_tests(metafunc: pytest.Metafunc) -> None: + """Mark all tests in this subdir as not valid for Monad forks.""" + metafunc.definition.add_marker( + pytest.mark.not_valid_for("MONAD_EIGHT", subsequent_forks=True) + ) diff --git a/tests/amsterdam/eip7976_increase_calldata_floor_cost/conftest.py b/tests/amsterdam/eip7976_increase_calldata_floor_cost/conftest.py index f92a0e003a0..9a252257297 100644 --- a/tests/amsterdam/eip7976_increase_calldata_floor_cost/conftest.py +++ b/tests/amsterdam/eip7976_increase_calldata_floor_cost/conftest.py @@ -370,3 +370,10 @@ def tx( blob_versioned_hashes=blob_versioned_hashes, error=tx_error, ) + + +def pytest_generate_tests(metafunc: pytest.Metafunc) -> None: + """Mark all tests in this subdir as not valid for Monad forks.""" + metafunc.definition.add_marker( + pytest.mark.not_valid_for("MONAD_EIGHT", subsequent_forks=True) + ) diff --git a/tests/amsterdam/eip7997_deterministic_factory_predeploy/test_factory.py b/tests/amsterdam/eip7997_deterministic_factory_predeploy/test_factory.py index bd4f9c3031d..9bb208616c2 100644 --- a/tests/amsterdam/eip7997_deterministic_factory_predeploy/test_factory.py +++ b/tests/amsterdam/eip7997_deterministic_factory_predeploy/test_factory.py @@ -30,6 +30,7 @@ compute_create2_address, keccak256, ) +from execution_testing.forks import MONAD_EIGHT from ...prague.eip7702_set_code_tx.spec import Spec as Spec7702 from .spec import Spec, ref_spec_7997 @@ -538,6 +539,7 @@ def test_factory_receives_balance_via_selfdestruct( def test_factory_via_eip7702_delegation( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """ An EOA delegates its code to the factory via an EIP-7702 @@ -549,6 +551,10 @@ def test_factory_via_eip7702_delegation( auth_signer = pre.fund_eoa() auth_signer_nonce = auth_signer.nonce + # Monad forbids the `CREATE2` of a delegated account's context, so + # the factory reverts there instead of deploying. + factory_reverts = fork >= MONAD_EIGHT + salt = 0x42 runtime_code = Op.PUSH1(0x01) + Op.PUSH1(0x00) + Op.RETURN initcode = Initcode(deploy_code=runtime_code) @@ -586,10 +592,12 @@ def test_factory_via_eip7702_delegation( ), post={ auth_signer: Account( - nonce=auth_signer_nonce + 2, + nonce=auth_signer_nonce + (1 if factory_reverts else 2), code=Spec7702.delegation_designation(Address(FACTORY)), ), - expected_address: Account(nonce=1, code=bytes(runtime_code)), + expected_address: Account.NONEXISTENT + if factory_reverts + else Account(nonce=1, code=bytes(runtime_code)), FACTORY: Account( nonce=1, balance=0, diff --git a/tests/amsterdam/eip8024_dupn_swapn_exchange/conftest.py b/tests/amsterdam/eip8024_dupn_swapn_exchange/conftest.py new file mode 100644 index 00000000000..d8c781bb489 --- /dev/null +++ b/tests/amsterdam/eip8024_dupn_swapn_exchange/conftest.py @@ -0,0 +1,10 @@ +"""Pytest (plugin) definitions local to EIP-8024 tests.""" + +import pytest + + +def pytest_generate_tests(metafunc: pytest.Metafunc) -> None: + """Mark all tests in this subdir as not valid for Monad forks.""" + metafunc.definition.add_marker( + pytest.mark.not_valid_for("MONAD_EIGHT", subsequent_forks=True) + ) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/conftest.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/conftest.py new file mode 100644 index 00000000000..2d16c275db4 --- /dev/null +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/conftest.py @@ -0,0 +1,10 @@ +"""Pytest (plugin) definitions local to EIP-8037 tests.""" + +import pytest + + +def pytest_generate_tests(metafunc: pytest.Metafunc) -> None: + """Mark all tests in this subdir as not valid for Monad forks.""" + metafunc.definition.add_marker( + pytest.mark.not_valid_for("MONAD_EIGHT", subsequent_forks=True) + ) diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/conftest.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/conftest.py new file mode 100644 index 00000000000..cd29eb50dcb --- /dev/null +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/conftest.py @@ -0,0 +1,10 @@ +"""Pytest (plugin) definitions local to EIP-8038 tests.""" + +import pytest + + +def pytest_generate_tests(metafunc: pytest.Metafunc) -> None: + """Mark all tests in this subdir as not valid for Monad forks.""" + metafunc.definition.add_marker( + pytest.mark.not_valid_for("MONAD_EIGHT", subsequent_forks=True) + ) diff --git a/tests/amsterdam/eip8070_sparse_blobpool/conftest.py b/tests/amsterdam/eip8070_sparse_blobpool/conftest.py index cb2a757494c..dc3ee7c3ecd 100644 --- a/tests/amsterdam/eip8070_sparse_blobpool/conftest.py +++ b/tests/amsterdam/eip8070_sparse_blobpool/conftest.py @@ -148,3 +148,10 @@ def txs( ) txs.append(network_wrapped_tx) return txs + + +def pytest_generate_tests(metafunc: pytest.Metafunc) -> None: + """Mark all tests in this subdir as not valid for Monad forks.""" + metafunc.definition.add_marker( + pytest.mark.not_valid_for("MONAD_EIGHT", subsequent_forks=True) + ) diff --git a/tests/amsterdam/eip8246_selfdestruct_no_burn/conftest.py b/tests/amsterdam/eip8246_selfdestruct_no_burn/conftest.py new file mode 100644 index 00000000000..4fb53dba402 --- /dev/null +++ b/tests/amsterdam/eip8246_selfdestruct_no_burn/conftest.py @@ -0,0 +1,10 @@ +"""Pytest (plugin) definitions local to EIP-8246 tests.""" + +import pytest + + +def pytest_generate_tests(metafunc: pytest.Metafunc) -> None: + """Mark all tests in this subdir as not valid for Monad forks.""" + metafunc.definition.add_marker( + pytest.mark.not_valid_for("MONAD_EIGHT", subsequent_forks=True) + ) diff --git a/tests/amsterdam/eip8282_builder_execution_requests/conftest.py b/tests/amsterdam/eip8282_builder_execution_requests/conftest.py index e17cee467fc..7a42ba76785 100644 --- a/tests/amsterdam/eip8282_builder_execution_requests/conftest.py +++ b/tests/amsterdam/eip8282_builder_execution_requests/conftest.py @@ -1,8 +1,17 @@ """Fixtures for the EIP-8282 builder execution request tests.""" +import pytest + from ...common.system_contract_request_fixtures import ( blocks, # noqa: F401 included_requests, # noqa: F401 system_contract_interactions_per_block_copy, # noqa: F401 timestamp, # noqa: F401 ) + + +def pytest_generate_tests(metafunc: pytest.Metafunc) -> None: + """Mark all tests in this subdir as not valid for Monad forks.""" + metafunc.definition.add_marker( + pytest.mark.not_valid_for("MONAD_EIGHT", subsequent_forks=True) + ) From 7916fb731a5a9020489a7536733ae275cac93103 Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Tue, 25 Aug 2026 10:51:08 +0000 Subject: [PATCH 2/2] refactor(forks): declare fork succession as a trait, not a keyword `follows()` sits with the other fork traits, so `__init_subclass__` is untouched and a successor fork inherits the order without restating it. Co-Authored-By: Claude --- .../src/execution_testing/forks/base_fork.py | 20 ++++++++++++++----- .../execution_testing/forks/forks/forks.py | 6 +++++- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/packages/testing/src/execution_testing/forks/base_fork.py b/packages/testing/src/execution_testing/forks/base_fork.py index 808d94b6045..38dea7ca65c 100644 --- a/packages/testing/src/execution_testing/forks/base_fork.py +++ b/packages/testing/src/execution_testing/forks/base_fork.py @@ -344,11 +344,13 @@ def _is_subclass_of(a: "BaseForkMeta", b: "BaseForkMeta") -> bool: b = BaseForkMeta._maybe_transitioned(b) if issubclass(a, b): return True - followed = getattr(a, "_follows", None) + # The metaclass sees its instances as plain classes, so the + # trait is reached through a cast, as elsewhere in this class. + followed = cast(Type["BaseFork"], a).follows() while followed is not None: if issubclass(followed, b): return True - followed = followed._follows + followed = followed.follows() return False def __gt__(cls, other: "BaseForkMeta") -> bool: @@ -392,7 +394,6 @@ class BaseFork(ForkOpcodeInterface, metaclass=BaseForkMeta): _fork_by_timestamp: ClassVar[bool] = False _blob_constants: ClassVar[Dict[str, int]] = {} _deployed: ClassVar[bool] = True - _follows: ClassVar[Optional[Type["BaseFork"]]] = None _enabled_eips: ClassVar[Set[int]] = set() _enabling_forks: ClassVar[Set[Type["BaseFork"]]] = set() @@ -408,7 +409,6 @@ def __init_subclass__( transition_tool_name: Optional[str] = None, ignore: bool = False, bpo_fork: bool = False, - follows: Optional[Type["BaseFork"]] = None, ruleset_name: Optional[str] = None, fork_by_timestamp: Optional[bool] = None, deployed: Optional[bool] = None, @@ -423,7 +423,6 @@ def __init_subclass__( forks. """ cls._transition_tool_name = transition_tool_name - cls._follows = follows cls._ignore = ignore cls._bpo_fork = bpo_fork cls._ruleset_name = ruleset_name @@ -1421,6 +1420,17 @@ def enabling_forks(cls) -> Set[Type["BaseFork"]]: raise Exception(f"Class {cls.__name__} is not an EIP.") return cls._enabling_forks + @classmethod + def follows(cls) -> Type["BaseFork"] | None: + """ + Return the fork this one comes after without inheriting it. + + A fork that reuses another lineage's ordering overrides this; + comparisons then place it after that fork, and after everything + that fork comes after, while its behavior stays its own. + """ + return None + @classmethod def parent(cls) -> Type["BaseFork"] | None: """Return the parent fork.""" diff --git a/packages/testing/src/execution_testing/forks/forks/forks.py b/packages/testing/src/execution_testing/forks/forks/forks.py index 55e96539cd7..10e722b2f37 100644 --- a/packages/testing/src/execution_testing/forks/forks/forks.py +++ b/packages/testing/src/execution_testing/forks/forks/forks.py @@ -1903,7 +1903,6 @@ def engine_payload_attribute_target_gas_limit(cls) -> bool: class MONAD_NEXT( # noqa: N801 eips.EIP7997, MONAD_TEN, - follows=Amsterdam, ): """ MONAD_NEXT fork. @@ -1914,4 +1913,9 @@ class MONAD_NEXT( # noqa: N801 MONAD_NEXT after Amsterdam through `follows`. """ + @classmethod + def follows(cls) -> type[BaseFork] | None: + """MONAD_NEXT comes after Amsterdam without inheriting it.""" + return Amsterdam + pass