Skip to content

Commit 0439522

Browse files
pdobaczmarioevz
andauthored
new(tests): EOF - EIP-4200 EIP-6206 RJUMPI with JUMPF (ethereum#928)
* new(tests): RJUMPI over an additional RETF * fix(fw): Mark JUMPF as unchecked like CALLF * new(tests): EOF - EIP-6206 - JUMPF/RJUMPx interactions * RJUMPF/CALLF test: feedback (.__members__ not needed) * Spot and remove dead code for new tests * Apply suggestions from code review * fix(tests): tox --------- Co-authored-by: Mario Vega <[email protected]>
1 parent 8caa0d5 commit 0439522

File tree

1 file changed

+126
-15
lines changed

1 file changed

+126
-15
lines changed

osaka/eip7692_eof_v1/eip5450_stack/test_code_validation.py

Lines changed: 126 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from ethereum_test_tools import EOFTestFiller
1313
from ethereum_test_tools.eof.v1 import Container, Section
1414
from ethereum_test_tools.vm.opcode import Opcodes as Op
15+
from ethereum_test_types.eof.v1.constants import NON_RETURNING_SECTION
1516
from ethereum_test_vm.bytecode import Bytecode
1617

1718
from .. import EOF_FORK_NAME
@@ -40,6 +41,7 @@ class RjumpKind(Enum):
4041
RJUMPI_TO_START = auto()
4142
RJUMPV_EMPTY_AND_OVER_NEXT = auto()
4243
RJUMPV_OVER_PUSH_AND_TO_START = auto()
44+
RJUMPI_OVER_RETF = auto()
4345

4446
def __str__(self) -> str:
4547
"""
@@ -119,6 +121,8 @@ def rjump_code_with(
119121
body = Op.RJUMPV[[1, -code_so_far_len - rjumpv_two_destinations_len]](0) + Op.PUSH0
120122
is_backwards = True
121123
pushes = True
124+
elif rjump_kind == RjumpKind.RJUMPI_OVER_RETF:
125+
body = Op.RJUMPI[1](0) + Op.RETF
122126
elif not rjump_kind:
123127
pass
124128
else:
@@ -172,7 +176,8 @@ def section_code_with(
172176
Also returns some traits of the section: `has_invalid_back_jump`, `rjump_snippet_pops`,
173177
`rjump_snippet_pushes`, `rjump_falls_off_code`
174178
"""
175-
code = Bytecode(min_stack_height=inputs, max_stack_height=inputs)
179+
code = Bytecode()
180+
code.pushed_stack_items, code.max_stack_height = (inputs, inputs)
176181

177182
if call:
178183
body = call_code_with(inputs, outputs, call)
@@ -188,6 +193,11 @@ def section_code_with(
188193
rjump, is_backwards, rjump_snippet_pops, rjump_snippet_pushes = rjump_code_with(
189194
rjump_kind, 0, body
190195
)
196+
if rjump_kind == RjumpKind.RJUMPI_OVER_RETF:
197+
if inputs > outputs:
198+
rjump_snippet_pushes = True
199+
elif outputs > inputs:
200+
rjump_snippet_pops = True
191201
code += rjump
192202

193203
code += body
@@ -200,11 +210,16 @@ def section_code_with(
200210

201211
if is_backwards and inputs != outputs:
202212
has_invalid_back_jump = True
213+
214+
if rjump_spot == RjumpSpot.BEFORE_TERMINATION or (
215+
rjump_spot == RjumpSpot.BEGINNING and len(termination) == 0
216+
):
203217
if rjump_kind in [
204218
RjumpKind.RJUMPI_OVER_NEXT,
205219
RjumpKind.RJUMPI_OVER_NEXT_NESTED,
206220
RjumpKind.RJUMPV_EMPTY_AND_OVER_NEXT,
207221
]:
222+
# Jump over termination or jump over body, but there is nothing after the body.
208223
rjump_falls_off_code = True
209224

210225
code += termination
@@ -231,15 +246,15 @@ def section_code_with(
231246
)
232247
@pytest.mark.parametrize(
233248
"rjump_kind",
234-
RjumpKind.__members__.values(),
249+
RjumpKind,
235250
)
236251
# Parameter value fixed for first iteration, to cover the most important case.
237252
@pytest.mark.parametrize("rjump_section_idx", [0, 1])
238253
@pytest.mark.parametrize(
239254
"rjump_spot",
240-
RjumpSpot.__members__.values(),
255+
RjumpSpot,
241256
)
242-
def test_eof_validity(
257+
def test_rjumps_callf_retf(
243258
eof_test: EOFTestFiller,
244259
inputs: Tuple[int, ...],
245260
outputs: Tuple[int, ...],
@@ -264,6 +279,10 @@ def test_eof_validity(
264279
container_has_rjump_pops = False
265280
container_has_rjump_pushes = False
266281
container_has_rjump_off_code = False
282+
container_has_section_0_retf = (
283+
rjump_section_idx == 0 and rjump_kind == RjumpKind.RJUMPI_OVER_RETF
284+
)
285+
267286
for section_idx in range(num_sections):
268287
if section_idx == 0:
269288
call = Op.CALLF[section_idx + 1]
@@ -298,15 +317,15 @@ def test_eof_validity(
298317
termination,
299318
)
300319

301-
container_has_invalid_back_jump = (
302-
container_has_invalid_back_jump or section_has_invalid_back_jump
303-
)
304-
container_has_rjump_pops = container_has_rjump_pops or rjump_snippet_pops
320+
if section_has_invalid_back_jump:
321+
container_has_invalid_back_jump = True
322+
if rjump_snippet_pops:
323+
container_has_rjump_pops = True
305324
# Pushes to the stack never affect the zeroth section, because it `STOP`s and not `RETF`s.
306-
container_has_rjump_pushes = container_has_rjump_pushes or (
307-
rjump_snippet_pushes and section_idx != 0
308-
)
309-
container_has_rjump_off_code = container_has_rjump_off_code or rjump_falls_off_code
325+
if rjump_snippet_pushes and section_idx != 0:
326+
container_has_rjump_pushes = True
327+
if rjump_falls_off_code:
328+
container_has_rjump_off_code = True
310329

311330
if section_idx > 0:
312331
sections.append(
@@ -328,7 +347,99 @@ def test_eof_validity(
328347
possible_exceptions.append(EOFException.STACK_HIGHER_THAN_OUTPUTS)
329348
if container_has_rjump_off_code:
330349
possible_exceptions.append(EOFException.INVALID_RJUMP_DESTINATION)
350+
if container_has_section_0_retf:
351+
possible_exceptions.append(EOFException.INVALID_NON_RETURNING_FLAG)
331352

332-
eof_test(
333-
data=bytes(Container(sections=sections)), expect_exception=possible_exceptions or None
334-
)
353+
eof_test(data=Container(sections=sections), expect_exception=possible_exceptions or None)
354+
355+
356+
@pytest.mark.parametrize(
357+
"inputs", itertools.product(*([possible_inputs_outputs] * (num_sections - 1)))
358+
)
359+
@pytest.mark.parametrize(
360+
"rjump_kind",
361+
RjumpKind,
362+
)
363+
# Parameter value fixed for first iteration, to cover the most important case.
364+
@pytest.mark.parametrize("rjump_section_idx", [0, 1])
365+
@pytest.mark.parametrize(
366+
"rjump_spot",
367+
# `termination` is empty for JUMPF codes, because JUMPF serves as one. Spot
368+
# `BEFORE_TERMINATION` is unreachable code.
369+
[k for k in RjumpSpot if k not in [RjumpSpot.BEFORE_TERMINATION]],
370+
)
371+
def test_rjumps_jumpf_nonreturning(
372+
eof_test: EOFTestFiller,
373+
inputs: Tuple[int, ...],
374+
rjump_kind: RjumpKind,
375+
rjump_section_idx: int,
376+
rjump_spot: RjumpSpot,
377+
):
378+
"""
379+
Test EOF container validaiton for EIP-4200 vs EIP-6206 interactions on non-returning
380+
functions.
381+
"""
382+
# Zeroth section has always 0 inputs and 0 outputs, so is excluded from param
383+
inputs = (0,) + inputs
384+
385+
sections = []
386+
container_has_rjump_pops = False
387+
container_has_rjump_off_code = False
388+
container_has_non_returning_retf = False
389+
390+
for section_idx in range(num_sections):
391+
if section_idx < num_sections - 1:
392+
call = Op.JUMPF[section_idx + 1]
393+
call.popped_stack_items = inputs[section_idx + 1]
394+
call.pushed_stack_items = 0
395+
call.min_stack_height = call.popped_stack_items
396+
call.max_stack_height = max(call.popped_stack_items, call.pushed_stack_items)
397+
termination = Bytecode()
398+
else:
399+
call = None
400+
termination = Op.STOP
401+
402+
# `section_has_invalid_back_jump` - never happens: we excluded RJUMP from the end
403+
# `rjump_snippet_pushes` - never happens: we never RETF where too large stack would fail
404+
(
405+
code,
406+
_section_has_invalid_back_jump,
407+
rjump_snippet_pops,
408+
_rjump_snippet_pushes,
409+
rjump_falls_off_code,
410+
) = section_code_with(
411+
inputs[section_idx],
412+
0,
413+
rjump_kind if rjump_section_idx == section_idx else None,
414+
rjump_spot,
415+
call,
416+
termination,
417+
)
418+
419+
if rjump_snippet_pops:
420+
container_has_rjump_pops = True
421+
if rjump_falls_off_code:
422+
container_has_rjump_off_code = True
423+
if rjump_kind == RjumpKind.RJUMPI_OVER_RETF:
424+
container_has_non_returning_retf = True
425+
426+
if section_idx > 0:
427+
sections.append(
428+
Section.Code(
429+
code,
430+
code_inputs=inputs[section_idx],
431+
code_outputs=NON_RETURNING_SECTION,
432+
)
433+
)
434+
else:
435+
sections.append(Section.Code(code))
436+
437+
possible_exceptions = []
438+
if container_has_rjump_pops:
439+
possible_exceptions.append(EOFException.STACK_UNDERFLOW)
440+
if container_has_rjump_off_code:
441+
possible_exceptions.append(EOFException.INVALID_RJUMP_DESTINATION)
442+
if container_has_non_returning_retf:
443+
possible_exceptions.append(EOFException.INVALID_NON_RETURNING_FLAG)
444+
445+
eof_test(data=Container(sections=sections), expect_exception=possible_exceptions or None)

0 commit comments

Comments
 (0)