Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tests/cancun/eip4788: Update to use pre-deployed contract #246

Merged
merged 3 commits into from
Aug 10, 2023
Merged
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
12 changes: 12 additions & 0 deletions src/ethereum_test_forks/base_decorators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""
Decorators for the fork methods.
"""


def prefer_transition_to_method(method):
"""
Decorator to mark a base method that must always call the `fork_to` implementation when
transitioning.
"""
method.__prefer_transition_to_method__ = True
return method
16 changes: 15 additions & 1 deletion src/ethereum_test_forks/base_fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
Abstract base class for Ethereum forks
"""
from abc import ABC, ABCMeta, abstractmethod
from typing import Optional, Type
from typing import Mapping, Optional, Type

from .base_decorators import prefer_transition_to_method


class BaseForkMeta(ABCMeta):
Expand Down Expand Up @@ -103,6 +105,18 @@ def get_reward(cls, block_number: int = 0, timestamp: int = 0) -> int:
"""
pass

@classmethod
@prefer_transition_to_method
@abstractmethod
def pre_allocation(cls, block_number: int = 0, timestamp: int = 0) -> Mapping:
"""
Returns required pre-allocation of accounts.

This method must always call the `fork_to` method when transitioning, because the
allocation can only be set at genesis, and thus cannot be changed at transition time.
"""
pass

# Engine API information abstract methods
@classmethod
@abstractmethod
Expand Down
26 changes: 25 additions & 1 deletion src/ethereum_test_forks/forks/forks.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""
All Ethereum fork class definitions.
"""
from typing import Optional
from typing import Mapping, Optional

from ..base_fork import BaseFork

Expand Down Expand Up @@ -99,6 +99,15 @@ def get_reward(cls, block_number: int = 0, timestamp: int = 0) -> int:
"""
return 5_000_000_000_000_000_000

@classmethod
def pre_allocation(cls, block_number: int = 0, timestamp: int = 0) -> Mapping:
"""
Returns whether the fork expects pre-allocation of accounts

Frontier does not require pre-allocated accounts
"""
return {}


class Homestead(Frontier):
"""
Expand Down Expand Up @@ -291,6 +300,21 @@ def header_beacon_root_required(cls, block_number: int = 0, timestamp: int = 0)
"""
return True

@classmethod
def pre_allocation(cls, block_number: int = 0, timestamp: int = 0) -> Mapping:
"""
Cancun requires pre-allocation of the beacon root contract for EIP-4788
"""
new_allocation = {
0x0B: {
"nonce": 1,
"code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604457602036146024575f5f"
"fd5b620180005f350680545f35146037575f5ffd5b6201800001545f5260205ff35b426201800042"
"06555f3562018000420662018000015500",
}
}
return new_allocation | super(Cancun, cls).pre_allocation(block_number, timestamp)

marioevz marked this conversation as resolved.
Show resolved Hide resolved
@classmethod
def engine_new_payload_version(
cls, block_number: int = 0, timestamp: int = 0
Expand Down
53 changes: 52 additions & 1 deletion src/ethereum_test_forks/tests/test_forks.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Test fork utilities.
"""

from typing import cast
from typing import Mapping, cast

from ..base_fork import Fork
from ..forks.forks import Berlin, Cancun, Frontier, London, Merge, Shanghai
Expand All @@ -17,6 +17,7 @@
transition_fork_from_to,
transition_fork_to,
)
from ..transition_base_fork import transition_fork

FIRST_DEPLOYED = Frontier
LAST_DEPLOYED = Shanghai
Expand Down Expand Up @@ -112,3 +113,53 @@ def test_deployed_forks(): # noqa: D103
deployed_forks = get_deployed_forks()
assert deployed_forks[0] == FIRST_DEPLOYED
assert deployed_forks[-1] == LAST_DEPLOYED


class PrePreAllocFork(Shanghai):
"""
Dummy fork used for testing.
"""

@classmethod
def pre_allocation(cls, block_number: int = 0, timestamp: int = 0) -> Mapping:
"""
Return some starting point for allocation.
"""
return {"test": "test"}


class PreAllocFork(PrePreAllocFork):
"""
Dummy fork used for testing.
"""

@classmethod
def pre_allocation(cls, block_number: int = 0, timestamp: int = 0) -> Mapping:
"""
Add allocation to the pre-existing one from previous fork.
"""
return {"test2": "test2"} | super(PreAllocFork, cls).pre_allocation(
block_number, timestamp
)


@transition_fork(to_fork=PreAllocFork, at_timestamp=15_000)
class PreAllocTransitionFork(PrePreAllocFork):
"""
PrePreAllocFork to PreAllocFork transition at Timestamp 15k
"""

pass


def test_pre_alloc():
assert PrePreAllocFork.pre_allocation() == {"test": "test"}
assert PreAllocFork.pre_allocation() == {"test": "test", "test2": "test2"}
assert PreAllocTransitionFork.pre_allocation() == {
"test": "test",
"test2": "test2",
}
assert PreAllocTransitionFork.pre_allocation(block_number=0, timestamp=0) == {
"test": "test",
"test2": "test2",
}
5 changes: 4 additions & 1 deletion src/ethereum_test_forks/transition_base_fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,14 @@ class NewTransitionClass(cls, TransitionBaseClass, BaseFork): # type: ignore

NewTransitionClass.name = lambda: transition_name # type: ignore

def make_transition_method(from_fork_method, to_fork_method):
def make_transition_method(base_method, from_fork_method, to_fork_method):
def transition_method(
cls,
block_number: int = ALWAYS_TRANSITIONED_BLOCK_NUMBER,
timestamp: int = ALWAYS_TRANSITIONED_BLOCK_TIMESTAMP,
):
if getattr(base_method, "__prefer_transition_to_method__", False):
return to_fork_method(block_number, timestamp)
return (
to_fork_method(block_number, timestamp)
if block_number >= at_block and timestamp >= at_timestamp
Expand All @@ -70,6 +72,7 @@ def transition_method(
NewTransitionClass,
method_name,
make_transition_method(
getattr(BaseFork, method_name),
getattr(from_fork, method_name),
getattr(to_fork, method_name),
),
Expand Down
44 changes: 43 additions & 1 deletion src/ethereum_test_tools/common/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -699,12 +699,54 @@ def with_code(cls: Type, code: BytesConvertible) -> "Account":
"""
return Account(nonce=1, code=code)

@classmethod
def merge(
cls: Type, account_1: "Dict | Account | None", account_2: "Dict | Account | None"
) -> "Account":
"""
Create a merged account from two sources.
"""

def to_kwargs_dict(account: "Dict | Account | None") -> Dict:
if account is None:
return {}
if isinstance(account, dict):
return account
elif isinstance(account, cls):
return {
f.name: v for f in fields(cls) if (v := getattr(account, f.name)) is not None
}
raise TypeError(f"Unexpected type for account merge: {type(account)}")

kwargs = to_kwargs_dict(account_1)
kwargs.update(to_kwargs_dict(account_2))

return cls(**kwargs)


class Alloc(dict, Mapping[FixedSizeBytesConvertible, Account | Dict], SupportsJSON):
class Alloc(dict, Mapping[Address, Account], SupportsJSON):
"""
Allocation of accounts in the state, pre and post test execution.
"""

def __init__(self, d: Mapping[FixedSizeBytesConvertible, Account | Dict] = {}):
for address, account in d.items():
address = Address(address)
assert address not in self, f"Duplicate address in alloc: {address}"
self[address] = Account.from_dict(account)

@classmethod
def merge(cls, alloc_1: "Alloc", alloc_2: "Alloc") -> "Alloc":
"""
Returns the merged allocation of two sources.
"""
merged = alloc_1.copy()

for address, other_account in alloc_2.items():
merged[address] = Account.merge(merged.get(address, None), other_account)

return Alloc(merged)

def __json__(self, encoder: JSONEncoder) -> Mapping[str, Any]:
"""
Returns the JSON representation of the allocation.
Expand Down
3 changes: 2 additions & 1 deletion src/ethereum_test_tools/spec/blockchain_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,9 @@ def make_genesis(
"""
env = self.genesis_environment.set_fork_requirements(fork)

pre_alloc = Alloc(fork.pre_allocation(block_number=0, timestamp=Number(env.timestamp)))
new_alloc, state_root = t8n.calc_state_root(
alloc=to_json(Alloc(self.pre)),
alloc=to_json(Alloc.merge(pre_alloc, Alloc(self.pre))),
fork=fork,
debug_output_path=self.get_next_transition_tool_output_path(),
)
Expand Down
4 changes: 3 additions & 1 deletion src/ethereum_test_tools/spec/state_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,10 @@ def make_genesis(

env = env.set_fork_requirements(fork)

pre_alloc = Alloc(fork.pre_allocation(block_number=0, timestamp=Number(env.timestamp)))

new_alloc, state_root = t8n.calc_state_root(
alloc=to_json(Alloc(self.pre)),
alloc=to_json(Alloc.merge(pre_alloc, Alloc(self.pre))),
fork=fork,
debug_output_path=self.get_next_transition_tool_output_path(),
)
Expand Down
69 changes: 69 additions & 0 deletions src/ethereum_test_tools/tests/test_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from ..common.constants import TestPrivateKey
from ..common.types import (
Address,
Alloc,
Bloom,
Bytes,
FixtureEngineNewPayload,
Expand Down Expand Up @@ -282,6 +283,74 @@ def test_account_check_alloc(account: Account, alloc: Dict[Any, Any], should_pas
account.check_alloc("test", alloc)


@pytest.mark.parametrize(
["alloc1", "alloc2", "expected_alloc"],
[
pytest.param(
Alloc(),
Alloc(),
Alloc(),
id="empty_alloc",
),
pytest.param(
Alloc({0x1: {"nonce": 1}}),
Alloc({0x2: {"nonce": 2}}),
Alloc({0x1: Account(nonce=1), 0x2: Account(nonce=2)}),
id="alloc_different_accounts",
),
pytest.param(
Alloc({0x2: {"nonce": 1}}),
Alloc({"0x02": {"nonce": 2}}),
Alloc({0x2: Account(nonce=2)}),
id="overwrite_account",
),
pytest.param(
Alloc({0x2: {"balance": 1}}),
Alloc({"0x02": {"nonce": 1}}),
Alloc({0x2: Account(balance=1, nonce=1)}),
id="mix_account",
),
],
)
def test_alloc_append(alloc1: Alloc, alloc2: Alloc, expected_alloc: Alloc):
assert Alloc.merge(alloc1, alloc2) == expected_alloc


@pytest.mark.parametrize(
["account1", "account2", "expected_account"],
[
pytest.param(
Account(),
Account(),
Account(),
id="empty_accounts",
),
pytest.param(
None,
None,
Account(),
id="none_accounts",
),
pytest.param(
Account(nonce=1),
Account(code="0x6000"),
Account(nonce=1, code="0x6000"),
id="accounts_with_different_fields",
),
pytest.param(
Account(nonce=1),
Account(nonce=2),
Account(nonce=2),
id="accounts_with_different_nonce",
),
],
)
def test_account_merge(
account1: Account | None, account2: Account | None, expected_account: Account
):
assert Account.merge(account1, account2) == expected_account


CHECKSUM_ADDRESS = "0x8a0A19589531694250d570040a0c4B74576919B8"


Expand Down
2 changes: 1 addition & 1 deletion src/evm_transition_tool/transition_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ def get_traces(self) -> List[List[List[Dict]]] | None:

def calc_state_root(
self, *, alloc: Any, fork: Fork, debug_output_path: str = ""
) -> Tuple[Dict[str, Any], bytes]:
) -> Tuple[Dict, bytes]:
"""
Calculate the state root for the given `alloc`.
"""
Expand Down
Loading