Skip to content

Commit 53fc6d8

Browse files
authored
new(tests): EOF - EIP-7069: Address Space Extension implications (#522)
* Validate EIP-7676 Prepare for Address Space Extension Validate ASE behaviors on legacy and EOF opcodes for ASE, when targeting EOAs, Contracts, and empty accounts. Opcodes are BALANCE, EXTCALL/CALL, EXTDELEGATECALL/DELEGATECALL, EXTSTATICCALL/STATICCALL, and CALLCODE. Signed-off-by: Danno Ferrin <[email protected]> * Reviewer requested changes Signed-off-by: Danno Ferrin <[email protected]> * formatting Signed-off-by: Danno Ferrin <[email protected]> * speling Signed-off-by: Danno Ferrin <[email protected]> * comment out balance from ASE checks EIP-7676 moved the critical EXTCALL sections into the main spec. EOF does not depend on a new BALANCE op, so don't test it. Signed-off-by: Danno Ferrin <[email protected]> * move to new subfolder Signed-off-by: Danno Ferrin <[email protected]> * Recast as EIP-7069 test Signed-off-by: Danno Ferrin <[email protected]> * use itertools.count instead of hard coded address Signed-off-by: Danno Ferrin <[email protected]> * Review changes * Apply specific edits * Parameterize by opcode as well Signed-off-by: Danno Ferrin <[email protected]> * tox fixes Signed-off-by: Danno Ferrin <[email protected]> * remove RETURNDATALOAD from ASE tests Remove RETURNDATALOAD from ASE tests, to be moved to a different test Signed-off-by: Danno Ferrin <[email protected]> * reviewer comments Signed-off-by: Danno Ferrin <[email protected]> * Reviewer comments Signed-off-by: Danno Ferrin <[email protected]> * extreneous changes Signed-off-by: Danno Ferrin <[email protected]> --------- Signed-off-by: Danno Ferrin <[email protected]>
1 parent 89b6cea commit 53fc6d8

File tree

4 files changed

+260
-0
lines changed

4 files changed

+260
-0
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"""
2+
Revamped Call Instructions Tests
3+
"""
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
"""
2+
EOF V1 Constants used throughout all tests
3+
"""
4+
5+
EOF_FORK_NAME = "CancunEIP7692"
6+
7+
CALL_FAILURE = 0
8+
CALL_SUCCESS = 1
9+
EXTCALL_SUCCESS = 0
10+
EXTCALL_REVERT = 1
11+
EXTCALL_FAILED = 2
Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
"""
2+
Tests the "Address Space Extension" aspect of EXT*CALL
3+
"""
4+
import itertools
5+
6+
import pytest
7+
8+
from ethereum_test_tools import (
9+
Account,
10+
Address,
11+
Environment,
12+
StateTestFiller,
13+
TestAddress,
14+
Transaction,
15+
)
16+
from ethereum_test_tools.eof.v1 import Container, Section
17+
from ethereum_test_tools.vm.opcode import Opcodes as Op
18+
19+
from .. import EOF_FORK_NAME
20+
from .spec import CALL_SUCCESS, EXTCALL_REVERT, EXTCALL_SUCCESS
21+
22+
REFERENCE_SPEC_GIT_PATH = "EIPS/eip-7069.md"
23+
REFERENCE_SPEC_VERSION = "1795943aeacc86131d5ab6bb3d65824b3b1d4cad"
24+
25+
pytestmark = pytest.mark.valid_from(EOF_FORK_NAME)
26+
27+
_address_allocation = itertools.count(0x10000)
28+
address_entry_point = Address(next(_address_allocation))
29+
address_caller = Address(next(_address_allocation))
30+
31+
_slot = itertools.count(1)
32+
slot_top_level_call_status = next(_slot)
33+
slot_target_call_status = next(_slot)
34+
slot_target_returndata = next(_slot)
35+
36+
value_exceptional_abort_canary = 0x1984
37+
38+
39+
@pytest.mark.parametrize(
40+
"target_address",
41+
(
42+
pytest.param(b"", id="zero"),
43+
pytest.param(b"\xc0\xde", id="short"),
44+
pytest.param(b"\x78" * 20, id="mid_20"),
45+
pytest.param(b"\xff" * 20, id="max_20"),
46+
pytest.param(b"\x01" + (b"\x00" * 20), id="min_ase"),
47+
pytest.param(b"\x5a" * 28, id="mid_ase"),
48+
pytest.param(b"\x5a" * 32, id="full_ase"),
49+
pytest.param(b"\xff" * 32, id="max_ase"),
50+
),
51+
)
52+
@pytest.mark.parametrize(
53+
"target_account_type", ("empty", "EOA", "LegacyContract", "EOFContract"), ids=lambda x: x
54+
)
55+
@pytest.mark.parametrize(
56+
"target_opcode",
57+
(
58+
Op.CALL,
59+
Op.CALLCODE,
60+
Op.STATICCALL,
61+
Op.DELEGATECALL,
62+
Op.EXTCALL,
63+
Op.EXTDELEGATECALL,
64+
Op.EXTSTATICCALL,
65+
),
66+
)
67+
def test_address_space_extension(
68+
state_test: StateTestFiller,
69+
target_address: bytes,
70+
target_opcode: Op,
71+
target_account_type: str,
72+
):
73+
"""
74+
Test contacts with possibly extended address and fail if address is too large
75+
"""
76+
env = Environment()
77+
78+
ase_address = len(target_address) > 20
79+
stripped_address = target_address[-20:] if ase_address else target_address
80+
if ase_address and target_address[0] == b"00":
81+
raise ValueError("Test instrumentation requires target addresses trim leading zeros")
82+
83+
match target_opcode:
84+
case Op.CALL | Op.CALLCODE:
85+
call_suffix = [0, 0, 0, 0, 0]
86+
ase_ready_opcode = False
87+
case Op.DELEGATECALL | Op.STATICCALL:
88+
call_suffix = [0, 0, 0, 0]
89+
ase_ready_opcode = False
90+
case Op.EXTCALL:
91+
call_suffix = [0, 0, 0]
92+
ase_ready_opcode = True
93+
case Op.EXTDELEGATECALL | Op.EXTSTATICCALL:
94+
call_suffix = [0, 0]
95+
ase_ready_opcode = True
96+
case _:
97+
raise ValueError("Unexpected opcode ", target_opcode)
98+
99+
pre = {
100+
TestAddress: Account(
101+
balance=1000000000000000000000,
102+
nonce=1,
103+
),
104+
address_entry_point: Account(
105+
code=(
106+
Op.MSTORE(0, Op.PUSH32(target_address))
107+
+ Op.SSTORE(
108+
slot_top_level_call_status,
109+
Op.CALL(50000, address_caller, 0, 0, 32, 0, 0),
110+
)
111+
+ Op.STOP()
112+
),
113+
nonce=1,
114+
storage={
115+
slot_top_level_call_status: value_exceptional_abort_canary,
116+
},
117+
),
118+
address_caller: Account(
119+
code=Container(
120+
sections=[
121+
Section.Code(
122+
code=Op.SSTORE(
123+
slot_target_call_status,
124+
target_opcode(Op.CALLDATALOAD(0), *call_suffix),
125+
)
126+
+ Op.RETURNDATACOPY(0, 0, Op.RETURNDATASIZE)
127+
+ Op.SSTORE(slot_target_returndata, Op.MLOAD(0))
128+
+ Op.STOP,
129+
code_inputs=0,
130+
max_stack_height=1 + len(call_suffix),
131+
)
132+
],
133+
)
134+
if ase_ready_opcode
135+
else Op.SSTORE(
136+
slot_target_call_status,
137+
target_opcode(Op.GAS, Op.CALLDATALOAD(0), *call_suffix),
138+
)
139+
+ Op.RETURNDATACOPY(0, 0, Op.RETURNDATASIZE)
140+
+ Op.SSTORE(slot_target_returndata, Op.MLOAD(0))
141+
+ Op.STOP,
142+
nonce=1,
143+
storage={
144+
slot_target_call_status: value_exceptional_abort_canary,
145+
slot_target_returndata: value_exceptional_abort_canary,
146+
},
147+
),
148+
}
149+
match target_account_type:
150+
case "empty":
151+
# add no account
152+
pass
153+
case "EOA":
154+
pre[Address(stripped_address)] = Account(code="", balance=10**18, nonce=9)
155+
case "LegacyContract":
156+
pre[Address(stripped_address)] = Account(
157+
code=Op.MSTORE(0, Op.ADDRESS) + Op.RETURN(0, 32),
158+
balance=0,
159+
nonce=0,
160+
)
161+
case "EOFContract":
162+
pre[Address(stripped_address)] = Account(
163+
code=Container(
164+
sections=[
165+
Section.Code(
166+
code=Op.MSTORE(0, Op.ADDRESS) + Op.RETURN(0, 32),
167+
max_stack_height=2,
168+
)
169+
],
170+
),
171+
balance=0,
172+
nonce=0,
173+
)
174+
175+
caller_storage: dict[int, int | bytes | Address] = {}
176+
match target_account_type:
177+
case "empty" | "EOA":
178+
if ase_address and ase_ready_opcode:
179+
caller_storage[slot_target_call_status] = value_exceptional_abort_canary
180+
caller_storage[slot_target_returndata] = value_exceptional_abort_canary
181+
elif target_opcode == Op.EXTDELEGATECALL:
182+
caller_storage[slot_target_call_status] = EXTCALL_REVERT
183+
caller_storage[slot_target_returndata] = 0
184+
else:
185+
caller_storage[slot_target_call_status] = (
186+
EXTCALL_SUCCESS if ase_ready_opcode else CALL_SUCCESS
187+
)
188+
case "LegacyContract" | "EOFContract":
189+
match target_opcode:
190+
case Op.CALL | Op.STATICCALL:
191+
caller_storage[slot_target_call_status] = CALL_SUCCESS
192+
# CALL and STATICCALL call will call the stripped address
193+
caller_storage[slot_target_returndata] = stripped_address
194+
case Op.CALLCODE | Op.DELEGATECALL:
195+
caller_storage[slot_target_call_status] = CALL_SUCCESS
196+
# CALLCODE and DELEGATECALL call will call the stripped address
197+
# but will change the sender to self
198+
caller_storage[slot_target_returndata] = address_caller
199+
case Op.EXTCALL | Op.EXTSTATICCALL:
200+
# EXTCALL and EXTSTATICCALL will fault if calling an ASE address
201+
if ase_address:
202+
caller_storage[slot_target_call_status] = value_exceptional_abort_canary
203+
caller_storage[slot_target_returndata] = value_exceptional_abort_canary
204+
else:
205+
caller_storage[slot_target_call_status] = EXTCALL_SUCCESS
206+
caller_storage[slot_target_returndata] = stripped_address
207+
case Op.EXTDELEGATECALL:
208+
if ase_address:
209+
caller_storage[slot_target_call_status] = value_exceptional_abort_canary
210+
caller_storage[slot_target_returndata] = value_exceptional_abort_canary
211+
elif target_account_type == "LegacyContract":
212+
caller_storage[slot_target_call_status] = EXTCALL_REVERT
213+
caller_storage[slot_target_returndata] = 0
214+
else:
215+
caller_storage[slot_target_call_status] = EXTCALL_SUCCESS
216+
# EXTDELEGATECALL call will call the stripped address
217+
# but will change the sender to self
218+
caller_storage[slot_target_returndata] = address_caller
219+
220+
post = {
221+
address_entry_point: Account(
222+
storage={
223+
slot_top_level_call_status: EXTCALL_SUCCESS
224+
if ase_ready_opcode and ase_address
225+
else CALL_SUCCESS
226+
}
227+
),
228+
address_caller: Account(storage=caller_storage),
229+
}
230+
231+
tx = Transaction(
232+
nonce=1,
233+
to=address_entry_point,
234+
gas_limit=50000000,
235+
gas_price=10,
236+
protected=False,
237+
data="",
238+
)
239+
240+
state_test(
241+
env=env,
242+
pre=pre,
243+
post=post,
244+
tx=tx,
245+
)

whitelist.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ acl
55
addr
66
address
77
address2
8+
ase
89
alloc
910
api
1011
apis

0 commit comments

Comments
 (0)