Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new(tests): EOF - EIP-4200 EIP-6206 RJUMPI with JUMPF #928

Merged
merged 7 commits into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
138 changes: 128 additions & 10 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 Down Expand Up @@ -239,7 +254,7 @@ def section_code_with(
"rjump_spot",
RjumpSpot.__members__.values(),
)
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,6 +347,105 @@ 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
)


@pytest.mark.parametrize(
"inputs", itertools.product(*([possible_inputs_outputs] * (num_sections - 1)))
)
@pytest.mark.parametrize(
"rjump_kind",
RjumpKind.__members__.values(),
)
# 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.__members__.values() 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_invalid_back_jump = False
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

(
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 section_has_invalid_back_jump:
container_has_invalid_back_jump = True
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_invalid_back_jump:
possible_exceptions.append(EOFException.STACK_HEIGHT_MISMATCH)
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=bytes(Container(sections=sections)), expect_exception=possible_exceptions or None
Expand Down
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
Loading