Skip to content

Commit c4a3d28

Browse files
committed
tests: Add initial set of beacon root tests.
1 parent 3b87d1d commit c4a3d28

File tree

6 files changed

+661
-5
lines changed

6 files changed

+661
-5
lines changed

src/ethereum_test_tools/common/types.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
from ..reference_spec.reference_spec import ReferenceSpec
1919
from .constants import (
2020
AddrAA,
21-
BeaconRoot,
2221
EmptyOmmersRoot,
2322
EngineAPIError,
2423
TestPrivateKey,
@@ -659,8 +658,11 @@ def set_fork_requirements(self, fork: Fork) -> "Environment":
659658
):
660659
res.data_gas_used = 0
661660

662-
if fork.header_beacon_root_required(self.number, self.timestamp):
663-
res.parent_beacon_block_root = BeaconRoot
661+
if (
662+
fork.header_beacon_root_required(self.number, self.timestamp)
663+
and res.parent_beacon_block_root is None
664+
):
665+
res.parent_beacon_block_root = bytes([0] * 32)
664666

665667
return res
666668

@@ -1771,8 +1773,8 @@ def default(self, obj: JSONEncoderSupportedType) -> Any:
17711773
b: Dict[str, Any] = {"rlp": hex_or_none(obj.rlp)}
17721774
if obj.block_header is not None:
17731775
b["blockHeader"] = json.loads(json.dumps(obj.block_header, cls=JSONEncoder))
1774-
if obj.new_payload is not None:
1775-
b["engineNewPayload"] = to_json_or_none(obj.new_payload)
1776+
# if obj.new_payload is not None:
1777+
# b["engineNewPayload"] = to_json_or_none(obj.new_payload)
17761778
if obj.expected_exception is not None:
17771779
b["expectException"] = obj.expected_exception
17781780
if obj.block_number is not None:
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"""
2+
Cross-client EIP-4788 Tests
3+
"""
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
"""
2+
Common constants, classes & functions local to EIP-4788 tests.
3+
"""
4+
5+
from ethereum_test_tools import (
6+
BeaconRoot,
7+
Storage,
8+
)
9+
10+
REF_SPEC_4788_GIT_PATH = "EIPS/eip-4788.md"
11+
REF_SPEC_4788_VERSION = "f0eb6a364aaf5ccb43516fa2c269a54fb881ecfd"
12+
13+
BEACON_ROOT_PRECOMPILE_ADDRESS = 0x0B # HISTORY_STORE_ADDRESS
14+
BEACON_ROOT_PRECOMPILE_GAS = 4_200 # G_BEACON_ROOT
15+
HISTORICAL_ROOTS_MODULUS = 98_304
16+
17+
FORK_TIMESTAMP = 15_000 # ShanghaiToCancun timestamp
18+
DEFAULT_BEACON_ROOT_HASH = BeaconRoot
19+
20+
21+
def timestamp_index(timestamp: int) -> int:
22+
"""
23+
Derive the timestamp index into the timestamp ring buffer.
24+
"""
25+
return timestamp % HISTORICAL_ROOTS_MODULUS
26+
27+
28+
def root_index(timestamp: int) -> int:
29+
"""
30+
Derive the root index into the root ring buffer.
31+
"""
32+
return timestamp_index(timestamp) + HISTORICAL_ROOTS_MODULUS
33+
34+
35+
def expected_storage(
36+
beacon_root: bytes,
37+
timestamp: int,
38+
valid_call: bool,
39+
valid_input: bool,
40+
) -> Storage:
41+
"""
42+
Derives the expected storage for a given beacon root precompile call
43+
dependent on:
44+
- success or failure of the call
45+
- validity of the timestamp input used within the call
46+
"""
47+
storage: Storage.StorageDictType = dict()
48+
# beacon root precompile call is successful
49+
if valid_call:
50+
storage[0] = 1
51+
storage[2] = 32
52+
# timestamp precompile input is valid
53+
if valid_input:
54+
# TODO: fix geth t8n to return the correct beacon root hash
55+
storage[1] = beacon_root
56+
storage[1] = 0
57+
else:
58+
storage[1] = 0
59+
storage[3] = storage[1]
60+
61+
# beacon root precompile call failed
62+
else:
63+
storage[0] = 0
64+
storage[1] = timestamp # due to failure, input is not overwritten
65+
storage[2] = 0
66+
storage[3] = storage[1]
67+
68+
return storage
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
"""
2+
Shared pytest definitions local to EIP-4788 tests.
3+
"""
4+
import pytest
5+
6+
from typing import Dict
7+
8+
from ethereum_test_tools import (
9+
Account,
10+
Environment,
11+
TestAddress,
12+
Transaction,
13+
to_hash_bytes,
14+
to_address,
15+
)
16+
17+
from .common import (
18+
timestamp_index,
19+
expected_storage,
20+
BEACON_ROOT_PRECOMPILE_GAS,
21+
BEACON_ROOT_PRECOMPILE_ADDRESS,
22+
DEFAULT_BEACON_ROOT_HASH,
23+
)
24+
25+
from ethereum_test_tools.vm.opcode import Opcodes as Op
26+
27+
28+
@pytest.fixture
29+
def timestamp() -> int: # noqa: D103
30+
return 0
31+
32+
33+
@pytest.fixture
34+
def beacon_root(request) -> bytes: # noqa: D103
35+
return to_hash_bytes(request.param) if hasattr(request, 'param') else DEFAULT_BEACON_ROOT_HASH
36+
37+
38+
@pytest.fixture
39+
def env(timestamp: int, beacon_root: bytes) -> Environment: # noqa: D103
40+
return Environment(
41+
timestamp=timestamp,
42+
parent_beacon_block_root=beacon_root,
43+
)
44+
45+
46+
@pytest.fixture
47+
def call_type() -> Op: # noqa: D103
48+
return Op.CALL
49+
50+
51+
@pytest.fixture
52+
def call_gas() -> int: # noqa: D103
53+
return BEACON_ROOT_PRECOMPILE_GAS
54+
55+
56+
@pytest.fixture
57+
def call_address() -> int: # noqa: D103
58+
return to_address(0x100)
59+
60+
61+
@pytest.fixture
62+
def precompile_call_account(call_type: Op, call_gas: int) -> Account:
63+
"""
64+
Code to call the beacon root precompile.
65+
"""
66+
precompile_call_code = Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE)
67+
if call_type == Op.CALL or call_type == Op.CALLCODE:
68+
precompile_call_code += Op.SSTORE(
69+
0, # store the result of the precompile call in storage[0]
70+
call_type(
71+
call_gas,
72+
BEACON_ROOT_PRECOMPILE_ADDRESS,
73+
0x00,
74+
0x00,
75+
Op.CALLDATASIZE,
76+
0x00,
77+
0x20,
78+
),
79+
)
80+
elif call_type == Op.DELEGATECALL or call_type == Op.STATICCALL:
81+
# delegatecall and staticcall use one less argument
82+
precompile_call_code += Op.SSTORE(
83+
0,
84+
call_type(
85+
call_gas,
86+
BEACON_ROOT_PRECOMPILE_ADDRESS,
87+
0x00,
88+
Op.CALLDATASIZE,
89+
0x00,
90+
0x20,
91+
),
92+
)
93+
precompile_call_code += (
94+
Op.SSTORE(1, Op.MLOAD(0x00))
95+
+ Op.SSTORE(2, Op.RETURNDATASIZE)
96+
+ Op.RETURNDATACOPY(0, 0x0, Op.RETURNDATASIZE)
97+
+ Op.SSTORE(3, Op.MLOAD(0x00))
98+
)
99+
return Account(
100+
nonce=0,
101+
code=precompile_call_code,
102+
balance=10**10,
103+
)
104+
105+
106+
@pytest.fixture
107+
def valid_call() -> bool:
108+
"""
109+
Validity of beacon root precompile call: defaults to True.
110+
"""
111+
return True
112+
113+
114+
@pytest.fixture
115+
def valid_input() -> bool:
116+
"""
117+
Validity of timestamp input to precompile call: defaults to True.
118+
"""
119+
return True
120+
121+
122+
@pytest.fixture
123+
def pre(
124+
precompile_call_account: Account,
125+
call_address: str,
126+
) -> Dict:
127+
"""
128+
Prepares the pre state of all test cases, by setting the balance of the
129+
source account of all test transactions, and the precompile caller account.
130+
"""
131+
return {
132+
TestAddress: Account(
133+
nonce=0,
134+
balance=0x10**10,
135+
),
136+
call_address: precompile_call_account,
137+
}
138+
139+
140+
@pytest.fixture
141+
def tx(
142+
call_address: str,
143+
timestamp: int,
144+
) -> Transaction:
145+
"""
146+
Prepares transaction to call the beacon root precompile caller account.
147+
"""
148+
return Transaction(
149+
ty=2,
150+
nonce=0,
151+
data=to_hash_bytes(timestamp_index(timestamp)),
152+
to=call_address,
153+
value=0,
154+
gas_limit=1000000,
155+
max_fee_per_gas=7,
156+
max_priority_fee_per_gas=0,
157+
)
158+
159+
160+
@pytest.fixture
161+
def post(
162+
call_address: str,
163+
beacon_root: bytes,
164+
timestamp: int,
165+
valid_call: bool,
166+
valid_input: bool,
167+
) -> Dict:
168+
"""
169+
Prepares the expected post state for a single precompile call based upon the success or
170+
failure of the call, and the validity of the timestamp input.
171+
"""
172+
return {
173+
call_address: Account(
174+
storage=expected_storage(
175+
beacon_root,
176+
timestamp,
177+
valid_call,
178+
valid_input,
179+
),
180+
),
181+
}

0 commit comments

Comments
 (0)