From ea4b01d22c5329072bf2dea2be6e18a77aa8c60f Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Wed, 19 Aug 2026 15:58:59 +0000 Subject: [PATCH 1/2] feat(monad_next): adopt EIP-7981 increase access list cost MONAD_NEXT charges access list bytes at its own floor token rate, ordered after Amsterdam by succession, and releases as the monad_eip7981 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 ++++++++---- src/ethereum/forks/monad_next/transactions.py | 36 ++++++++++++++++--- .../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_access_list_cost.py | 5 +-- .../test_fork_transition.py | 16 +++++---- .../conftest.py | 10 ++++++ .../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 +++++ 20 files changed, 208 insertions(+), 23 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/eip7997_deterministic_factory_predeploy/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..7849a5e116a 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_eip7981: + 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..04fde59cc0c 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.EIP7981, + MONAD_TEN, + follows=Amsterdam, +): + """ + MONAD_NEXT fork. + + Amsterdam-based successor to MONAD_TEN, adopting the EIP-7981 + 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/src/ethereum/forks/monad_next/transactions.py b/src/ethereum/forks/monad_next/transactions.py index 24bb4880a74..71bce9409f3 100644 --- a/src/ethereum/forks/monad_next/transactions.py +++ b/src/ethereum/forks/monad_next/transactions.py @@ -30,6 +30,22 @@ TX_MAX_GAS_LIMIT = Uint(30_000_000) +ACCESS_LIST_ADDRESS_FLOOR_TOKENS = Uint(80) +""" +Floor data tokens contributed by a single access list address per +[EIP-7981]. + +[EIP-7981]: https://eips.ethereum.org/EIPS/eip-7981 +""" + +ACCESS_LIST_STORAGE_KEY_FLOOR_TOKENS = Uint(128) +""" +Floor data tokens contributed by a single access list storage key per +[EIP-7981]. + +[EIP-7981]: https://eips.ethereum.org/EIPS/eip-7981 +""" + @final @slotted_freezable @@ -587,10 +603,6 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: num_non_zeros = ulen(tx.data) - num_zeros tokens_in_calldata = num_zeros + num_non_zeros * Uint(4) - # EIP-7623 floor price (note: no EVM costs) - calldata_floor_gas_cost = ( - tokens_in_calldata * GasCosts.TX_DATA_TOKEN_FLOOR + GasCosts.TX_BASE - ) data_cost = tokens_in_calldata * GasCosts.TX_DATA_TOKEN_STANDARD @@ -600,12 +612,28 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: create_cost = Uint(0) access_list_cost = Uint(0) + tokens_in_access_list = Uint(0) if has_access_list(tx): for access in tx.access_list: access_list_cost += GasCosts.TX_ACCESS_LIST_ADDRESS access_list_cost += ( ulen(access.slots) * GasCosts.TX_ACCESS_LIST_STORAGE_KEY ) + tokens_in_access_list += ACCESS_LIST_ADDRESS_FLOOR_TOKENS + tokens_in_access_list += ( + ulen(access.slots) * ACCESS_LIST_STORAGE_KEY_FLOOR_TOKENS + ) + + # Data token floor cost for access list bytes. + access_list_cost += tokens_in_access_list * GasCosts.TX_DATA_TOKEN_FLOOR + + # Total floor tokens. + total_floor_tokens = tokens_in_calldata + tokens_in_access_list + + # EIP-7623 floor price (note: no EVM costs) + calldata_floor_gas_cost = ( + total_floor_tokens * GasCosts.TX_DATA_TOKEN_FLOOR + GasCosts.TX_BASE + ) auth_cost = Uint(0) if isinstance(tx, SetCodeTransaction): 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/eip7981_increase_access_list_cost/test_access_list_cost.py b/tests/amsterdam/eip7981_increase_access_list_cost/test_access_list_cost.py index 2d093e6d0bb..981617c0ec0 100644 --- a/tests/amsterdam/eip7981_increase_access_list_cost/test_access_list_cost.py +++ b/tests/amsterdam/eip7981_increase_access_list_cost/test_access_list_cost.py @@ -124,10 +124,11 @@ def test_access_list_token_calculation( expected_floor_cost = ( expected_floor_tokens * gas_costs.TX_DATA_TOKEN_FLOOR + gas_costs.TX_BASE + ) + if fork.is_eip_enabled(2780): # EIP-2780 anchors the floor on the decomposed intrinsic base; the # tx targets a non-self account, adding the recipient-access charge. - + gas_costs.COLD_ACCOUNT_ACCESS - ) + expected_floor_cost += gas_costs.COLD_ACCOUNT_ACCESS actual_floor_cost = fork.transaction_data_floor_cost_calculator()( data=b"", access_list=access_list ) diff --git a/tests/amsterdam/eip7981_increase_access_list_cost/test_fork_transition.py b/tests/amsterdam/eip7981_increase_access_list_cost/test_fork_transition.py index 62a899b346a..92b02d7def7 100644 --- a/tests/amsterdam/eip7981_increase_access_list_cost/test_fork_transition.py +++ b/tests/amsterdam/eip7981_increase_access_list_cost/test_fork_transition.py @@ -7,12 +7,13 @@ transition timestamp) and pin the per-transaction gas paid on each side, plus the validity flip for gas limits inside the uplift gap. -The post-fork intrinsic composes three repricings; the hand-derived +The post-fork intrinsic composes up to three repricings; the hand-derived expectations below keep each term explicit so the EIP-7981 surcharge is individually visible: -- EIP-2780 decomposes the flat pre-fork `TX_BASE` into the lowered base - plus the `COLD_ACCOUNT_ACCESS` recipient charge. +- EIP-2780, on the forks that enable it, decomposes the flat pre-fork + `TX_BASE` into the lowered base plus the `COLD_ACCOUNT_ACCESS` + recipient charge. - EIP-8038 reprices the per-address and per-storage-key access list charges to the fork's cold access costs. - EIP-7981 adds four floor tokens per access list byte, charged at @@ -116,11 +117,12 @@ def test_access_list_intrinsic_across_amsterdam_transition( ) expected_post = ( post_costs.TX_BASE - + post_costs.COLD_ACCOUNT_ACCESS + addresses * post_costs.TX_ACCESS_LIST_ADDRESS + total_keys * post_costs.TX_ACCESS_LIST_STORAGE_KEY + surcharge ) + if post_fork.is_eip_enabled(2780): + expected_post += post_costs.COLD_ACCOUNT_ACCESS timestamps = [PRE_FORK_TIMESTAMP, POST_FORK_TIMESTAMP] expected_intrinsics = [expected_pre, expected_post] @@ -322,10 +324,10 @@ def test_access_list_floor_across_amsterdam_transition( post_costs.TX_DATA_TOKEN_STANDARD ) + calculate_access_list_floor_tokens(access_list) expected_post = int( - post_costs.TX_BASE - + post_costs.COLD_ACCOUNT_ACCESS - + post_tokens * post_costs.TX_DATA_TOKEN_FLOOR + post_costs.TX_BASE + post_tokens * post_costs.TX_DATA_TOKEN_FLOOR ) + if post_fork.is_eip_enabled(2780): + expected_post += int(post_costs.COLD_ACCOUNT_ACCESS) timestamps = [PRE_FORK_TIMESTAMP, POST_FORK_TIMESTAMP] expected_floors = [expected_pre, expected_post] diff --git a/tests/amsterdam/eip7997_deterministic_factory_predeploy/conftest.py b/tests/amsterdam/eip7997_deterministic_factory_predeploy/conftest.py new file mode 100644 index 00000000000..de93abfcfef --- /dev/null +++ b/tests/amsterdam/eip7997_deterministic_factory_predeploy/conftest.py @@ -0,0 +1,10 @@ +"""Pytest (plugin) definitions local to EIP-7997 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/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 1faef38e4baab1bd12fc049f4334962c7fba4b41 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 04fde59cc0c..a4a8218052b 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.EIP7981, 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