Skip to content

Commit

Permalink
new(tests): EOF - EIP-4200 EIP-6206 RJUMPI with JUMPF (ethereum#928)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
pdobacz and marioevz authored Nov 1, 2024
1 parent e6d3cdc commit cfbfea9
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 16 deletions.
2 changes: 1 addition & 1 deletion src/ethereum_test_vm/opcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -4959,7 +4959,7 @@ class Opcodes(Opcode, Enum):
3
"""

JUMPF = Opcode(0xE5, data_portion_length=2, terminating=True)
JUMPF = Opcode(0xE5, data_portion_length=2, terminating=True, unchecked_stack=True)
"""
!!! Note: This opcode is under development
Expand Down
141 changes: 126 additions & 15 deletions tests/osaka/eip7692_eof_v1/eip5450_stack/test_code_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from ethereum_test_tools import EOFTestFiller
from ethereum_test_tools.eof.v1 import Container, Section
from ethereum_test_tools.vm.opcode import Opcodes as Op
from ethereum_test_types.eof.v1.constants import NON_RETURNING_SECTION
from ethereum_test_vm.bytecode import Bytecode

from .. import EOF_FORK_NAME
Expand Down Expand Up @@ -40,6 +41,7 @@ class RjumpKind(Enum):
RJUMPI_TO_START = auto()
RJUMPV_EMPTY_AND_OVER_NEXT = auto()
RJUMPV_OVER_PUSH_AND_TO_START = auto()
RJUMPI_OVER_RETF = auto()

def __str__(self) -> str:
"""
Expand Down Expand Up @@ -119,6 +121,8 @@ def rjump_code_with(
body = Op.RJUMPV[[1, -code_so_far_len - rjumpv_two_destinations_len]](0) + Op.PUSH0
is_backwards = True
pushes = True
elif rjump_kind == RjumpKind.RJUMPI_OVER_RETF:
body = Op.RJUMPI[1](0) + Op.RETF
elif not rjump_kind:
pass
else:
Expand Down Expand Up @@ -172,7 +176,8 @@ def section_code_with(
Also returns some traits of the section: `has_invalid_back_jump`, `rjump_snippet_pops`,
`rjump_snippet_pushes`, `rjump_falls_off_code`
"""
code = Bytecode(min_stack_height=inputs, max_stack_height=inputs)
code = Bytecode()
code.pushed_stack_items, code.max_stack_height = (inputs, inputs)

if call:
body = call_code_with(inputs, outputs, call)
Expand All @@ -188,6 +193,11 @@ def section_code_with(
rjump, is_backwards, rjump_snippet_pops, rjump_snippet_pushes = rjump_code_with(
rjump_kind, 0, body
)
if rjump_kind == RjumpKind.RJUMPI_OVER_RETF:
if inputs > outputs:
rjump_snippet_pushes = True
elif outputs > inputs:
rjump_snippet_pops = True
code += rjump

code += body
Expand All @@ -200,11 +210,16 @@ def section_code_with(

if is_backwards and inputs != outputs:
has_invalid_back_jump = True

if rjump_spot == RjumpSpot.BEFORE_TERMINATION or (
rjump_spot == RjumpSpot.BEGINNING and len(termination) == 0
):
if rjump_kind in [
RjumpKind.RJUMPI_OVER_NEXT,
RjumpKind.RJUMPI_OVER_NEXT_NESTED,
RjumpKind.RJUMPV_EMPTY_AND_OVER_NEXT,
]:
# Jump over termination or jump over body, but there is nothing after the body.
rjump_falls_off_code = True

code += termination
Expand All @@ -231,15 +246,15 @@ def section_code_with(
)
@pytest.mark.parametrize(
"rjump_kind",
RjumpKind.__members__.values(),
RjumpKind,
)
# Parameter value fixed for first iteration, to cover the most important case.
@pytest.mark.parametrize("rjump_section_idx", [0, 1])
@pytest.mark.parametrize(
"rjump_spot",
RjumpSpot.__members__.values(),
RjumpSpot,
)
def test_eof_validity(
def test_rjumps_callf_retf(
eof_test: EOFTestFiller,
inputs: Tuple[int, ...],
outputs: Tuple[int, ...],
Expand All @@ -264,6 +279,10 @@ def test_eof_validity(
container_has_rjump_pops = False
container_has_rjump_pushes = False
container_has_rjump_off_code = False
container_has_section_0_retf = (
rjump_section_idx == 0 and rjump_kind == RjumpKind.RJUMPI_OVER_RETF
)

for section_idx in range(num_sections):
if section_idx == 0:
call = Op.CALLF[section_idx + 1]
Expand Down Expand Up @@ -298,15 +317,15 @@ def test_eof_validity(
termination,
)

container_has_invalid_back_jump = (
container_has_invalid_back_jump or section_has_invalid_back_jump
)
container_has_rjump_pops = container_has_rjump_pops or rjump_snippet_pops
if section_has_invalid_back_jump:
container_has_invalid_back_jump = True
if rjump_snippet_pops:
container_has_rjump_pops = True
# Pushes to the stack never affect the zeroth section, because it `STOP`s and not `RETF`s.
container_has_rjump_pushes = container_has_rjump_pushes or (
rjump_snippet_pushes and section_idx != 0
)
container_has_rjump_off_code = container_has_rjump_off_code or rjump_falls_off_code
if rjump_snippet_pushes and section_idx != 0:
container_has_rjump_pushes = True
if rjump_falls_off_code:
container_has_rjump_off_code = True

if section_idx > 0:
sections.append(
Expand All @@ -328,7 +347,99 @@ def test_eof_validity(
possible_exceptions.append(EOFException.STACK_HIGHER_THAN_OUTPUTS)
if container_has_rjump_off_code:
possible_exceptions.append(EOFException.INVALID_RJUMP_DESTINATION)
if container_has_section_0_retf:
possible_exceptions.append(EOFException.INVALID_NON_RETURNING_FLAG)

eof_test(
data=bytes(Container(sections=sections)), expect_exception=possible_exceptions or None
)
eof_test(data=Container(sections=sections), expect_exception=possible_exceptions or None)


@pytest.mark.parametrize(
"inputs", itertools.product(*([possible_inputs_outputs] * (num_sections - 1)))
)
@pytest.mark.parametrize(
"rjump_kind",
RjumpKind,
)
# Parameter value fixed for first iteration, to cover the most important case.
@pytest.mark.parametrize("rjump_section_idx", [0, 1])
@pytest.mark.parametrize(
"rjump_spot",
# `termination` is empty for JUMPF codes, because JUMPF serves as one. Spot
# `BEFORE_TERMINATION` is unreachable code.
[k for k in RjumpSpot if k not in [RjumpSpot.BEFORE_TERMINATION]],
)
def test_rjumps_jumpf_nonreturning(
eof_test: EOFTestFiller,
inputs: Tuple[int, ...],
rjump_kind: RjumpKind,
rjump_section_idx: int,
rjump_spot: RjumpSpot,
):
"""
Test EOF container validaiton for EIP-4200 vs EIP-6206 interactions on non-returning
functions.
"""
# Zeroth section has always 0 inputs and 0 outputs, so is excluded from param
inputs = (0,) + inputs

sections = []
container_has_rjump_pops = False
container_has_rjump_off_code = False
container_has_non_returning_retf = False

for section_idx in range(num_sections):
if section_idx < num_sections - 1:
call = Op.JUMPF[section_idx + 1]
call.popped_stack_items = inputs[section_idx + 1]
call.pushed_stack_items = 0
call.min_stack_height = call.popped_stack_items
call.max_stack_height = max(call.popped_stack_items, call.pushed_stack_items)
termination = Bytecode()
else:
call = None
termination = Op.STOP

# `section_has_invalid_back_jump` - never happens: we excluded RJUMP from the end
# `rjump_snippet_pushes` - never happens: we never RETF where too large stack would fail
(
code,
_section_has_invalid_back_jump,
rjump_snippet_pops,
_rjump_snippet_pushes,
rjump_falls_off_code,
) = section_code_with(
inputs[section_idx],
0,
rjump_kind if rjump_section_idx == section_idx else None,
rjump_spot,
call,
termination,
)

if rjump_snippet_pops:
container_has_rjump_pops = True
if rjump_falls_off_code:
container_has_rjump_off_code = True
if rjump_kind == RjumpKind.RJUMPI_OVER_RETF:
container_has_non_returning_retf = True

if section_idx > 0:
sections.append(
Section.Code(
code,
code_inputs=inputs[section_idx],
code_outputs=NON_RETURNING_SECTION,
)
)
else:
sections.append(Section.Code(code))

possible_exceptions = []
if container_has_rjump_pops:
possible_exceptions.append(EOFException.STACK_UNDERFLOW)
if container_has_rjump_off_code:
possible_exceptions.append(EOFException.INVALID_RJUMP_DESTINATION)
if container_has_non_returning_retf:
possible_exceptions.append(EOFException.INVALID_NON_RETURNING_FLAG)

eof_test(data=Container(sections=sections), expect_exception=possible_exceptions or None)
1 change: 1 addition & 0 deletions whitelist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,7 @@ jumpdest
rjump
rjumpi
rjumpkind
rjumps
rjumpv
RJUMPV
callf
Expand Down

0 comments on commit cfbfea9

Please sign in to comment.