From 9b2fe40178821500fe2496de602bc3132a1a8e89 Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Thu, 24 Oct 2024 09:14:02 +0200 Subject: [PATCH 1/7] new(tests): RJUMPI over an additional RETF --- .../eip5450_stack/test_code_validation.py | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/tests/osaka/eip7692_eof_v1/eip5450_stack/test_code_validation.py b/tests/osaka/eip7692_eof_v1/eip5450_stack/test_code_validation.py index 6a2f852e36e..f15b591949c 100644 --- a/tests/osaka/eip7692_eof_v1/eip5450_stack/test_code_validation.py +++ b/tests/osaka/eip7692_eof_v1/eip5450_stack/test_code_validation.py @@ -40,6 +40,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: """ @@ -119,6 +120,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: @@ -188,6 +191,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 @@ -264,6 +272,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] @@ -298,15 +310,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( @@ -328,6 +340,8 @@ 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 From 2c8307b7bfef75f06ad01c86188c14ecc70e36d3 Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Mon, 28 Oct 2024 11:01:57 +0100 Subject: [PATCH 2/7] fix(fw): Mark JUMPF as unchecked like CALLF --- src/ethereum_test_vm/opcode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ethereum_test_vm/opcode.py b/src/ethereum_test_vm/opcode.py index 195c93809b2..dbac86b0f0e 100644 --- a/src/ethereum_test_vm/opcode.py +++ b/src/ethereum_test_vm/opcode.py @@ -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 From 1012ab78776ad3c7a200de28cfa802bc6a87b6a9 Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Mon, 28 Oct 2024 11:05:12 +0100 Subject: [PATCH 3/7] new(tests): EOF - EIP-6206 - JUMPF/RJUMPx interactions --- .../eip5450_stack/test_code_validation.py | 108 +++++++++++++++++- whitelist.txt | 1 + 2 files changed, 107 insertions(+), 2 deletions(-) diff --git a/tests/osaka/eip7692_eof_v1/eip5450_stack/test_code_validation.py b/tests/osaka/eip7692_eof_v1/eip5450_stack/test_code_validation.py index f15b591949c..9bb3b33dc6c 100644 --- a/tests/osaka/eip7692_eof_v1/eip5450_stack/test_code_validation.py +++ b/tests/osaka/eip7692_eof_v1/eip5450_stack/test_code_validation.py @@ -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 @@ -175,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) @@ -208,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 @@ -247,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, ...], @@ -346,3 +353,100 @@ def test_eof_validity( 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 + ) diff --git a/whitelist.txt b/whitelist.txt index 7eaa4fa2f2e..1e8486f13b8 100644 --- a/whitelist.txt +++ b/whitelist.txt @@ -631,6 +631,7 @@ jumpdest rjump rjumpi rjumpkind +rjumps rjumpv RJUMPV callf From 1e86a73c6c26edb6b0260bcfd9ee5cf68fc2009c Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Thu, 31 Oct 2024 09:23:36 +0100 Subject: [PATCH 4/7] RJUMPF/CALLF test: feedback (.__members__ not needed) --- .../eip7692_eof_v1/eip5450_stack/test_code_validation.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/osaka/eip7692_eof_v1/eip5450_stack/test_code_validation.py b/tests/osaka/eip7692_eof_v1/eip5450_stack/test_code_validation.py index 9bb3b33dc6c..20fb6ec288a 100644 --- a/tests/osaka/eip7692_eof_v1/eip5450_stack/test_code_validation.py +++ b/tests/osaka/eip7692_eof_v1/eip5450_stack/test_code_validation.py @@ -246,13 +246,13 @@ 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_rjumps_callf_retf( eof_test: EOFTestFiller, @@ -360,7 +360,7 @@ def test_rjumps_callf_retf( ) @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]) @@ -368,7 +368,7 @@ def test_rjumps_callf_retf( "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]], + [k for k in RjumpSpot if k not in [RjumpSpot.BEFORE_TERMINATION]], ) def test_rjumps_jumpf_nonreturning( eof_test: EOFTestFiller, From f818c6fce97f12d70f8355fd5bde024d79954955 Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Thu, 31 Oct 2024 11:34:45 +0100 Subject: [PATCH 5/7] Spot and remove dead code for new tests --- .../eip5450_stack/test_code_validation.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/tests/osaka/eip7692_eof_v1/eip5450_stack/test_code_validation.py b/tests/osaka/eip7692_eof_v1/eip5450_stack/test_code_validation.py index 20fb6ec288a..569b77437ea 100644 --- a/tests/osaka/eip7692_eof_v1/eip5450_stack/test_code_validation.py +++ b/tests/osaka/eip7692_eof_v1/eip5450_stack/test_code_validation.py @@ -385,7 +385,6 @@ def test_rjumps_jumpf_nonreturning( 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 @@ -402,11 +401,13 @@ def test_rjumps_jumpf_nonreturning( 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, + _section_has_invalid_back_jump, rjump_snippet_pops, - rjump_snippet_pushes, + _rjump_snippet_pushes, rjump_falls_off_code, ) = section_code_with( inputs[section_idx], @@ -417,8 +418,6 @@ def test_rjumps_jumpf_nonreturning( 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: @@ -438,8 +437,6 @@ def test_rjumps_jumpf_nonreturning( 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: From 77a18d16309c6675f3123b4b7c071fde37b39291 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Thu, 31 Oct 2024 17:20:02 -0600 Subject: [PATCH 6/7] Apply suggestions from code review --- .../eip7692_eof_v1/eip5450_stack/test_code_validation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/osaka/eip7692_eof_v1/eip5450_stack/test_code_validation.py b/tests/osaka/eip7692_eof_v1/eip5450_stack/test_code_validation.py index 569b77437ea..76aab911624 100644 --- a/tests/osaka/eip7692_eof_v1/eip5450_stack/test_code_validation.py +++ b/tests/osaka/eip7692_eof_v1/eip5450_stack/test_code_validation.py @@ -351,7 +351,7 @@ def test_rjumps_callf_retf( possible_exceptions.append(EOFException.INVALID_NON_RETURNING_FLAG) eof_test( - data=bytes(Container(sections=sections)), expect_exception=possible_exceptions or None + data=Container(sections=sections), expect_exception=possible_exceptions or None ) @@ -445,5 +445,5 @@ def test_rjumps_jumpf_nonreturning( possible_exceptions.append(EOFException.INVALID_NON_RETURNING_FLAG) eof_test( - data=bytes(Container(sections=sections)), expect_exception=possible_exceptions or None + data=Container(sections=sections), expect_exception=possible_exceptions or None ) From f2a55d11d9af84d54df7911dc605b11d9b504f25 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Fri, 1 Nov 2024 16:05:47 +0000 Subject: [PATCH 7/7] fix(tests): tox --- .../eip7692_eof_v1/eip5450_stack/test_code_validation.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/osaka/eip7692_eof_v1/eip5450_stack/test_code_validation.py b/tests/osaka/eip7692_eof_v1/eip5450_stack/test_code_validation.py index 76aab911624..63ea9295289 100644 --- a/tests/osaka/eip7692_eof_v1/eip5450_stack/test_code_validation.py +++ b/tests/osaka/eip7692_eof_v1/eip5450_stack/test_code_validation.py @@ -350,9 +350,7 @@ def test_rjumps_callf_retf( if container_has_section_0_retf: possible_exceptions.append(EOFException.INVALID_NON_RETURNING_FLAG) - eof_test( - data=Container(sections=sections), expect_exception=possible_exceptions or None - ) + eof_test(data=Container(sections=sections), expect_exception=possible_exceptions or None) @pytest.mark.parametrize( @@ -444,6 +442,4 @@ def test_rjumps_jumpf_nonreturning( 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 - ) + eof_test(data=Container(sections=sections), expect_exception=possible_exceptions or None)