Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/configs/feature.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Original file line number Diff line number Diff line change
Expand Up @@ -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."
)
Expand Down
30 changes: 27 additions & 3 deletions packages/testing/src/execution_testing/forks/base_fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,12 +333,25 @@ 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
# 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()
return False

def __gt__(cls, other: "BaseForkMeta") -> bool:
"""Compare if a fork is newer than some other fork (cls > other)."""
Expand Down Expand Up @@ -1407,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."""
Expand Down
27 changes: 21 additions & 6 deletions packages/testing/src/execution_testing/forks/forks/forks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -1904,3 +1898,24 @@ def engine_payload_attribute_target_gas_limit(cls) -> bool:
limit.
"""
return True


class MONAD_NEXT( # noqa: N801
eips.EIP7981,
MONAD_TEN,
):
"""
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`.
"""

@classmethod
def follows(cls) -> type[BaseFork] | None:
"""MONAD_NEXT comes after Amsterdam without inheriting it."""
return Amsterdam

pass
36 changes: 32 additions & 4 deletions src/ethereum/forks/monad_next/transactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -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):
Expand Down
10 changes: 10 additions & 0 deletions tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/conftest.py
Original file line number Diff line number Diff line change
@@ -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)
)
10 changes: 10 additions & 0 deletions tests/amsterdam/eip7708_eth_transfer_logs/conftest.py
Original file line number Diff line number Diff line change
@@ -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)
)
Original file line number Diff line number Diff line change
@@ -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)
)
10 changes: 10 additions & 0 deletions tests/amsterdam/eip7843_slotnum/conftest.py
Original file line number Diff line number Diff line change
@@ -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)
)
10 changes: 10 additions & 0 deletions tests/amsterdam/eip7928_block_level_access_lists/conftest.py
Original file line number Diff line number Diff line change
@@ -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)
)
Original file line number Diff line number Diff line change
Expand Up @@ -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)
)
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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]
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
)
10 changes: 10 additions & 0 deletions tests/amsterdam/eip8024_dupn_swapn_exchange/conftest.py
Original file line number Diff line number Diff line change
@@ -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)
)
Original file line number Diff line number Diff line change
@@ -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)
)
Original file line number Diff line number Diff line change
@@ -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)
)
7 changes: 7 additions & 0 deletions tests/amsterdam/eip8070_sparse_blobpool/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
)
10 changes: 10 additions & 0 deletions tests/amsterdam/eip8246_selfdestruct_no_burn/conftest.py
Original file line number Diff line number Diff line change
@@ -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)
)
Original file line number Diff line number Diff line change
@@ -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)
)