Skip to content

Commit de57ee5

Browse files
shemnonmarioevz
andauthored
new(tests): EOF - EIP-7069 - RETURNDATALOAD and RETURNDATACOPY (#595)
* 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]> * new(tests) EIP-7069 RETURNDATALOAD and RETURNDATACOPY Test new RETURNDATACOPY semantics across both EOF and Legacy. Test RETURNDATALOAD semantics. Includes boundary conditions on both. Signed-off-by: Danno Ferrin <[email protected]> * review changes Signed-off-by: Danno Ferrin <[email protected]> * unneeded changes. Signed-off-by: Danno Ferrin <[email protected]> * docs: Changelog --------- Signed-off-by: Danno Ferrin <[email protected]> Co-authored-by: Mario Vega <[email protected]>
1 parent 5c590c6 commit de57ee5

File tree

5 files changed

+299
-2
lines changed

5 files changed

+299
-2
lines changed

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Test fixtures for use by clients are available for each release on the [Github r
1818
- ✨ Add tests for [EIP-7685: General purpose execution layer requests](https://eips.ethereum.org/EIPS/eip-7685) ([#530](https://github.com/ethereum/execution-spec-tests/pull/530)).
1919
- ✨ Add tests for [EIP-2935: Serve historical block hashes from state](https://eips.ethereum.org/EIPS/eip-2935) ([#564](https://github.com/ethereum/execution-spec-tests/pull/564)).
2020
- ✨ Add tests for [EIP-4200: EOF - Static relative jumps](https://eips.ethereum.org/EIPS/eip-4200) ([#581](https://github.com/ethereum/execution-spec-tests/pull/581)).
21+
- ✨ Add tests for [EIP-7069: EOF - Revamped CALL instructions](https://eips.ethereum.org/EIPS/eip-7069) ([#595](https://github.com/ethereum/execution-spec-tests/pull/595)).
2122
- 🐞 Fix typos in self-destruct collision test from erroneous pytest parametrization ([#608](https://github.com/ethereum/execution-spec-tests/pull/608)).
2223

2324
### 🛠️ Framework
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
"""
22
Revamped Call Instructions Tests
33
"""
4+
5+
REFERENCE_SPEC_GIT_PATH = "EIPS/eip-7069.md"
6+
REFERENCE_SPEC_VERSION = "1795943aeacc86131d5ab6bb3d65824b3b1d4cad"
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
"""
2+
EOF extcall tests helpers
3+
"""
4+
import itertools
5+
6+
"""Storage addresses for common testing fields"""
7+
_slot = itertools.count()
8+
next(_slot) # don't use slot 0
9+
slot_code_worked = next(_slot)
10+
slot_last_slot = next(_slot)
11+
12+
"""Storage values for common testing fields"""
13+
value_code_worked = 0x2015

tests/prague/eip7692_eof_v1/eip7069_extcall/spec.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
EOF V1 Constants used throughout all tests
33
"""
44

5-
EOF_FORK_NAME = "CancunEIP7692"
6-
75
CALL_FAILURE = 0
86
CALL_SUCCESS = 1
97
EXTCALL_SUCCESS = 0
Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
"""
2+
abstract: Tests [EIP-7069: Revamped CALL instructions](https://eips.ethereum.org/EIPS/eip-7069)
3+
Tests for the RETURNDATALOAD instriction
4+
""" # noqa: E501
5+
from typing import List
6+
7+
import pytest
8+
9+
from ethereum_test_tools import (
10+
Account,
11+
Address,
12+
Environment,
13+
StateTestFiller,
14+
TestAddress,
15+
Transaction,
16+
)
17+
from ethereum_test_tools.eof.v1 import Container, Section
18+
from ethereum_test_tools.eof.v1.constants import NON_RETURNING_SECTION
19+
from ethereum_test_tools.vm.opcode import Opcodes as Op
20+
21+
from .. import EOF_FORK_NAME
22+
from . import REFERENCE_SPEC_GIT_PATH, REFERENCE_SPEC_VERSION
23+
from .helpers import slot_code_worked, value_code_worked
24+
25+
REFERENCE_SPEC_GIT_PATH = REFERENCE_SPEC_GIT_PATH
26+
REFERENCE_SPEC_VERSION = REFERENCE_SPEC_VERSION
27+
28+
pytestmark = pytest.mark.valid_from(EOF_FORK_NAME)
29+
30+
31+
@pytest.mark.parametrize(
32+
["call_prefix", "opcode", "call_suffix"],
33+
[
34+
pytest.param([500_000], Op.CALL, [0, 0, 0, 0, 0], id="CALL"),
35+
pytest.param([500_000], Op.CALLCODE, [0, 0, 0, 0, 0], id="CALLCODE"),
36+
pytest.param([500_000], Op.DELEGATECALL, [0, 0, 0, 0], id="DELEGATECALL"),
37+
pytest.param([500_000], Op.STATICCALL, [0, 0, 0, 0], id="STATICCALL"),
38+
pytest.param([], Op.EXTCALL, [0, 0, 0], id="EXTCALL"),
39+
pytest.param([], Op.EXTDELEGATECALL, [0, 0], id="EXTDELEGATECALL"),
40+
pytest.param([], Op.EXTSTATICCALL, [0, 0], id="EXTSTATICCALL"),
41+
],
42+
ids=lambda x: x,
43+
)
44+
@pytest.mark.parametrize(
45+
"return_data",
46+
[
47+
b"",
48+
b"\x10" * 0x10,
49+
b"\x20" * 0x20,
50+
b"\x30" * 0x30,
51+
],
52+
ids=lambda x: "len_%x" % len(x),
53+
)
54+
@pytest.mark.parametrize(
55+
"offset",
56+
[
57+
0,
58+
0x10,
59+
0x20,
60+
0x30,
61+
],
62+
ids=lambda x: "offset_%x" % x,
63+
)
64+
@pytest.mark.parametrize(
65+
"size",
66+
[
67+
0,
68+
0x10,
69+
0x20,
70+
0x30,
71+
],
72+
ids=lambda x: "size_%x" % x,
73+
)
74+
def test_returndatacopy_handling(
75+
state_test: StateTestFiller,
76+
call_prefix: List[int],
77+
opcode: Op,
78+
call_suffix: List[int],
79+
return_data: bytes,
80+
offset: int,
81+
size: int,
82+
):
83+
"""
84+
Tests ReturnDataLoad including multiple offset conditions and differeing legacy vs. eof
85+
boundary conditions.
86+
87+
entrypoint creates a "0xff" test area of memory, delegate calls to caller.
88+
Caller is either EOF or legacy, as per parameter. Calls returner and copies the return data
89+
based on offset and size params. Cases are expected to trigger boundary violations.
90+
91+
Entrypoint copies the test area to storage slots, and the expected result is asserted.
92+
"""
93+
env = Environment()
94+
address_entry_point = Address(0x1000000)
95+
address_caller = Address(0x1000001)
96+
address_returner = Address(0x1000002)
97+
tx = Transaction(to=address_entry_point, gas_limit=2_000_000, nonce=1)
98+
99+
slot_result_start = 0x1000
100+
101+
pre = {
102+
TestAddress: Account(balance=10**18, nonce=tx.nonce),
103+
address_entry_point: Account(
104+
nonce=1,
105+
code=Op.NOOP
106+
# First, create a "dirty" area, so we can check zero overwrite
107+
+ Op.MSTORE(0x00, -1) + Op.MSTORE(0x20, -1)
108+
# call the contract under test
109+
+ Op.DELEGATECALL(1_000_000, address_caller, 0, 0, 0, 0)
110+
+ Op.RETURNDATACOPY(0, 0, Op.RETURNDATASIZE)
111+
# store the return data
112+
+ Op.SSTORE(slot_result_start, Op.MLOAD(0x0))
113+
+ Op.SSTORE(slot_result_start + 1, Op.MLOAD(0x20))
114+
+ Op.SSTORE(slot_code_worked, value_code_worked)
115+
+ Op.STOP,
116+
),
117+
address_returner: Account(
118+
nonce=1,
119+
code=Container(
120+
sections=[
121+
Section.Code(
122+
code=Op.DATACOPY(0, 0, Op.DATASIZE) + Op.RETURN(0, Op.DATASIZE),
123+
code_outputs=NON_RETURNING_SECTION,
124+
max_stack_height=3,
125+
),
126+
Section.Data(data=return_data),
127+
]
128+
),
129+
),
130+
}
131+
132+
result = [0xFF] * 0x40
133+
result[0:size] = [0] * size
134+
extent = size - max(0, size + offset - len(return_data))
135+
if extent > 0 and len(return_data) > 0:
136+
result[0:extent] = [return_data[0]] * extent
137+
post = {
138+
address_entry_point: Account(
139+
storage={
140+
slot_code_worked: value_code_worked,
141+
slot_result_start: bytes(result[:0x20]),
142+
(slot_result_start + 0x1): bytes(result[0x20:]),
143+
}
144+
)
145+
}
146+
147+
code_under_test: bytes = (
148+
opcode(*call_prefix, address_returner, *call_suffix)
149+
+ Op.RETURNDATACOPY(0, offset, size)
150+
+ Op.SSTORE(slot_code_worked, value_code_worked)
151+
+ Op.RETURN(0, size)
152+
)
153+
match opcode:
154+
case Op.EXTCALL | Op.EXTDELEGATECALL | Op.EXTSTATICCALL:
155+
pre[address_caller] = Account(
156+
code=Container(
157+
sections=[
158+
Section.Code(
159+
code=code_under_test,
160+
max_stack_height=4,
161+
)
162+
]
163+
)
164+
)
165+
case Op.CALL | Op.CALLCODE | Op.DELEGATECALL | Op.STATICCALL:
166+
pre[address_caller] = Account(
167+
code=code_under_test,
168+
)
169+
if (offset + size) > len(return_data):
170+
post[address_entry_point] = Account(
171+
storage={
172+
slot_code_worked: value_code_worked,
173+
slot_result_start: b"\xff" * 32,
174+
slot_result_start + 1: b"\xff" * 32,
175+
}
176+
)
177+
178+
state_test(
179+
env=env,
180+
pre=pre,
181+
tx=tx,
182+
post=post,
183+
)
184+
185+
186+
@pytest.mark.parametrize(
187+
["opcode", "call_suffix"],
188+
[
189+
pytest.param(Op.EXTCALL, [0, 0, 0], id="EXTCALL"),
190+
pytest.param(Op.EXTDELEGATECALL, [0, 0], id="EXTDELEGATECALL"),
191+
pytest.param(Op.EXTSTATICCALL, [0, 0], id="EXTSTATICCALL"),
192+
],
193+
ids=lambda x: x,
194+
)
195+
@pytest.mark.parametrize(
196+
"return_data",
197+
[
198+
b"",
199+
b"\x10" * 0x10,
200+
b"\x20" * 0x20,
201+
b"\x30" * 0x30,
202+
],
203+
ids=lambda x: "len_%x" % len(x),
204+
)
205+
@pytest.mark.parametrize(
206+
"offset",
207+
[
208+
0,
209+
0x10,
210+
0x20,
211+
0x30,
212+
],
213+
ids=lambda x: "offset_%x" % x,
214+
)
215+
def test_returndataload_handling(
216+
state_test: StateTestFiller,
217+
opcode: Op,
218+
call_suffix: List[int],
219+
return_data: bytes,
220+
offset: int,
221+
):
222+
"""
223+
Much simpler than returndatacopy, no memory or boosted call. Returner is called
224+
and results are stored in storage slot, which is asserted for expected values.
225+
The parameters offset and return data are configured to test boundary conditions.
226+
"""
227+
env = Environment()
228+
address_entry_point = Address(0x1000000)
229+
address_returner = Address(0x1000001)
230+
tx = Transaction(to=address_entry_point, gas_limit=2_000_000, nonce=1)
231+
232+
slot_result_start = 0x1000
233+
234+
pre = {
235+
TestAddress: Account(balance=10**18, nonce=tx.nonce),
236+
address_entry_point: Account(
237+
nonce=1,
238+
code=Container(
239+
sections=[
240+
Section.Code(
241+
code=opcode(address_returner, *call_suffix)
242+
+ Op.SSTORE(slot_result_start, Op.RETURNDATALOAD(offset))
243+
+ Op.SSTORE(slot_code_worked, value_code_worked)
244+
+ Op.STOP,
245+
max_stack_height=len(call_suffix) + 1,
246+
)
247+
]
248+
),
249+
),
250+
address_returner: Account(
251+
nonce=1,
252+
code=Container(
253+
sections=[
254+
Section.Code(
255+
code=Op.DATACOPY(0, 0, Op.DATASIZE) + Op.RETURN(0, Op.DATASIZE),
256+
max_stack_height=3,
257+
),
258+
Section.Data(data=return_data),
259+
]
260+
),
261+
),
262+
}
263+
264+
result = [0] * 0x20
265+
extent = 0x20 - max(0, 0x20 + offset - len(return_data))
266+
if extent > 0 and len(return_data) > 0:
267+
result[0:extent] = [return_data[0]] * extent
268+
post = {
269+
address_entry_point: Account(
270+
storage={
271+
slot_code_worked: value_code_worked,
272+
slot_result_start: bytes(result),
273+
}
274+
)
275+
}
276+
277+
state_test(
278+
env=env,
279+
pre=pre,
280+
tx=tx,
281+
post=post,
282+
)

0 commit comments

Comments
 (0)