Skip to content

Commit 9d18733

Browse files
authored
fix(test-forks): treat transition fork variants as equal to canonical (ethereum#2782)
* test(test-forks): add failing test for transition fork variant equality Cover the case where `with_env_gas_limit` variants of a transition fork compare unequal to their canonical class and to each other, breaking pre-alloc group reuse during fill. * fix(test-forks): treat transition fork variants as equal to canonical `TransitionBaseClass.with_env_gas_limit` produced new class objects that compared unequal via the default `type.__eq__`, breaking pre-alloc group reuse during fill (assertion in `add_test_pre`). Mirror `BaseForkMeta` by adding `__eq__`/`__hash__` on `TransitionBaseMetaClass` keyed on the canonical identity, and set `_base_fork` on the variant inside `with_env_gas_limit`. Widen `_identity`/`_maybe_transitioned` signatures to `type` so the metaclass can reuse them.
1 parent e4c43d6 commit 9d18733

3 files changed

Lines changed: 53 additions & 9 deletions

File tree

packages/testing/src/execution_testing/forks/base_fork.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ def __repr__(cls) -> str:
184184
return cls.name()
185185

186186
@staticmethod
187-
def _maybe_transitioned(fork_cls: "BaseForkMeta") -> "BaseForkMeta":
187+
def _maybe_transitioned(fork_cls: type) -> type:
188188
"""
189189
Return the transitioned fork, if a transition fork, otherwise return
190190
`fork_cls`.
@@ -203,14 +203,14 @@ def _is_subclass_of(a: "BaseForkMeta", b: "BaseForkMeta") -> bool:
203203
"""
204204
# Resolve variants to canonical identity so comparisons between
205205
# a variant and a canonical descendant fork work as expected.
206-
a = BaseForkMeta._identity(a)
207-
b = BaseForkMeta._identity(b)
208-
a = BaseForkMeta._maybe_transitioned(a)
209-
b = BaseForkMeta._maybe_transitioned(b)
210-
return issubclass(a, b)
206+
a_id: type = BaseForkMeta._identity(a)
207+
b_id: type = BaseForkMeta._identity(b)
208+
a_id = BaseForkMeta._maybe_transitioned(a_id)
209+
b_id = BaseForkMeta._maybe_transitioned(b_id)
210+
return issubclass(a_id, b_id)
211211

212212
@staticmethod
213-
def _identity(fork_cls: "BaseForkMeta") -> "BaseForkMeta":
213+
def _identity(fork_cls: type) -> type:
214214
"""Return the canonical fork class, resolving variants."""
215215
base = getattr(fork_cls, "_base_fork", None)
216216
return base if base is not None else fork_cls

packages/testing/src/execution_testing/forks/tests/test_forks.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -754,3 +754,32 @@ def test_fork_variant_ordering() -> None:
754754
assert not (variant < London)
755755
assert variant >= London
756756
assert variant <= London
757+
758+
759+
def test_transition_fork_variant_equality() -> None:
760+
"""
761+
Variants of a transition fork created via `with_env_gas_limit` must
762+
compare equal to their canonical parent and to each other, even when
763+
different gas limits are used. Distinct canonical transition forks
764+
must remain unequal.
765+
"""
766+
canonical = CancunToPragueAtTime15k
767+
variant_a = canonical.with_env_gas_limit(30_000_000)
768+
variant_b = canonical.with_env_gas_limit(45_000_000)
769+
variant_c = canonical.with_env_gas_limit(30_000_000)
770+
771+
assert variant_a is not canonical
772+
assert variant_a is not variant_b
773+
assert variant_a is not variant_c
774+
775+
assert variant_a == canonical
776+
assert variant_b == canonical
777+
assert variant_a == variant_b
778+
assert variant_a == variant_c
779+
780+
assert hash(variant_a) == hash(canonical)
781+
assert hash(variant_b) == hash(canonical)
782+
assert hash(variant_c) == hash(canonical)
783+
784+
assert canonical != PragueToOsakaAtTime15k
785+
assert variant_a != PragueToOsakaAtTime15k

packages/testing/src/execution_testing/forks/transition_base_fork.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
"""Base objects used to define transition forks."""
22

3-
from typing import Any, Callable, ClassVar, Dict, Type
3+
from typing import Any, Callable, ClassVar, Dict, Optional, Type
44

5-
from .base_fork import BaseFork
5+
from .base_fork import BaseFork, BaseForkMeta
66

77

88
class TransitionBaseMetaClass(type):
@@ -15,6 +15,19 @@ def name(cls) -> str:
1515
"""
1616
raise Exception("Not implemented")
1717

18+
def __eq__(cls, other: object) -> bool:
19+
"""
20+
Compare transition fork identity, treating variants created by
21+
`with_env_gas_limit` as equal to their canonical parent.
22+
"""
23+
if not isinstance(other, type):
24+
return NotImplemented
25+
return BaseForkMeta._identity(cls) is BaseForkMeta._identity(other)
26+
27+
def __hash__(cls) -> int:
28+
"""Hash by canonical transition fork identity."""
29+
return id(BaseForkMeta._identity(cls))
30+
1831
def transitions_to(cls) -> Type[BaseFork]:
1932
"""
2033
Return fork where the transition ends.
@@ -76,6 +89,7 @@ class TransitionBaseClass(metaclass=TransitionBaseMetaClass):
7689
at_timestamp: ClassVar[int] = 0
7790
_ignore: ClassVar[bool] = False
7891
_env_gas_limit: ClassVar[int] = 0
92+
_base_fork: ClassVar[Optional[Type["TransitionBaseClass"]]] = None
7993

8094
@classmethod
8195
def fork_at(
@@ -117,6 +131,7 @@ def with_env_gas_limit(
117131
"""
118132
new_cls = type(cls.__name__, (cls,), {})
119133
new_cls._env_gas_limit = env_gas_limit # type: ignore[attr-defined]
134+
new_cls._base_fork = cls._base_fork or cls # type: ignore[attr-defined]
120135
return new_cls
121136

122137

0 commit comments

Comments
 (0)