From 73f5f8492fdfe6834939526895089a52922f7cf0 Mon Sep 17 00:00:00 2001 From: Dimitry Kh Date: Thu, 2 May 2024 10:45:38 +0200 Subject: [PATCH 1/9] fix formatting, add eofparse string to whitelist --- src/ethereum_test_tools/spec/eof/eof_test.py | 8 ++++---- whitelist.txt | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/ethereum_test_tools/spec/eof/eof_test.py b/src/ethereum_test_tools/spec/eof/eof_test.py index 79cbef8677..a38803b17b 100644 --- a/src/ethereum_test_tools/spec/eof/eof_test.py +++ b/src/ethereum_test_tools/spec/eof/eof_test.py @@ -46,9 +46,9 @@ class UnexpectedEOFException(EOFBaseException): def __init__(self, *, code: Bytes, got: str): message = ( "Expected EOF code to be valid, but an exception occurred:\n" - f" Code: {self.format_code(code)}\n" - "Expected: No Exception\n" - f" Got: {got}" + f" Code: {self.format_code(code)}\n" + f"Expected: No Exception\n" + f" Got: {got}" ) super().__init__(message) @@ -64,7 +64,7 @@ def __init__(self, *, code: Bytes, expected: str): "Expected EOF code to be invalid, but no exception was raised:\n" f" Code: {self.format_code(code)}\n" f"Expected: {expected}\n" - " Got: No Exception" + f" Got: No Exception" ) super().__init__(message) diff --git a/whitelist.txt b/whitelist.txt index 74b078db00..4c8ed43eb1 100644 --- a/whitelist.txt +++ b/whitelist.txt @@ -99,6 +99,7 @@ enum env envvar eof +eofparse EOFException esbenp eth From 4965c362c53b8c3d1d2bb139bcc1448462ad5885 Mon Sep 17 00:00:00 2001 From: Dimitry Kh Date: Tue, 30 Apr 2024 15:14:45 +0200 Subject: [PATCH 2/9] translate Ori's validInvalid eof tests --- .../exceptions/evmone_exceptions.py | 4 + .../exceptions/exceptions.py | 13 + .../test_example_validInvalid.py | 499 ++++++++++++++++++ 3 files changed, 516 insertions(+) create mode 100644 tests/prague/eip3540_eof_v1/test_example_validInvalid.py diff --git a/src/ethereum_test_tools/exceptions/evmone_exceptions.py b/src/ethereum_test_tools/exceptions/evmone_exceptions.py index e9b5a7abe3..dc074b1c01 100644 --- a/src/ethereum_test_tools/exceptions/evmone_exceptions.py +++ b/src/ethereum_test_tools/exceptions/evmone_exceptions.py @@ -1,6 +1,7 @@ """ Evmone eof exceptions ENUM -> str mapper """ + from dataclasses import dataclass from bidict import frozenbidict @@ -44,6 +45,9 @@ class EvmoneExceptionMapper: ExceptionMessage(EOFException.INCOMPLETE_SECTION_NUMBER, "err: incomplete_section_number"), ExceptionMessage(EOFException.TOO_MANY_CODE_SECTIONS, "err: too_many_code_sections"), ExceptionMessage(EOFException.ZERO_SECTION_SIZE, "err: zero_section_size"), + ExceptionMessage(EOFException.UNDEFINED_INSTRUCTION, "err: undefined_instruction"), + ExceptionMessage(EOFException.UNREACHABLE_INSTRUCTIONS, "err: unreachable_instructions"), + ExceptionMessage(EOFException.INVALID_RJUMP_DESTINATION, "err: invalid_rjump_destination"), ) def __init__(self) -> None: diff --git a/src/ethereum_test_tools/exceptions/exceptions.py b/src/ethereum_test_tools/exceptions/exceptions.py index f3d175eb11..6aadcf2e56 100644 --- a/src/ethereum_test_tools/exceptions/exceptions.py +++ b/src/ethereum_test_tools/exceptions/exceptions.py @@ -198,6 +198,11 @@ class EOFException(ExceptionBase): Indicates that exception string is not mapped to an exception enum """ + UNDEFINED_INSTRUCTION = auto() + """ + EOF container has undefined instruction in it's body code + """ + UNKNOWN_VERSION = auto() """ EOF container has an unknown version @@ -214,6 +219,10 @@ class EOFException(ExceptionBase): """ EOF container version bytes mismatch """ + INVALID_RJUMP_DESTINATION = auto() + """ + Code has RJUMP instruction with invalid parameters + """ MISSING_TYPE_HEADER = auto() """ EOF container missing types section @@ -286,6 +295,10 @@ class EOFException(ExceptionBase): """ EOF container's code missing STOP bytecode at it's end """ + UNREACHABLE_INSTRUCTIONS = auto() + """ + EOF container's code have instructions that are unreachable + """ """ diff --git a/tests/prague/eip3540_eof_v1/test_example_validInvalid.py b/tests/prague/eip3540_eof_v1/test_example_validInvalid.py new file mode 100644 index 0000000000..0de7ef5755 --- /dev/null +++ b/tests/prague/eip3540_eof_v1/test_example_validInvalid.py @@ -0,0 +1,499 @@ +""" +EOF Classes example use +""" + +import pytest + +from ethereum_test_tools import EOFTestFiller, Opcode +from ethereum_test_tools import Opcodes as Op +from ethereum_test_tools.eof.v1 import Bytes, Container, EOFException, Section +from ethereum_test_tools.eof.v1.constants import NON_RETURNING_SECTION + +from .spec import EOF_FORK_NAME + +REFERENCE_SPEC_GIT_PATH = "EIPS/eip-3540.md" +REFERENCE_SPEC_VERSION = "8dcb0a8c1c0102c87224308028632cc986a61183" + +pytestmark = pytest.mark.valid_from(EOF_FORK_NAME) + + +def test_EOF1V0001(eof_test: EOFTestFiller): + """ + Check that simple EOF1 deploys + """ + + eof_code = Container( + name="EOF1V0001", + sections=[ + Section.Code( + code=Op.ADDRESS + Op.POP + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data("0xef"), + ], + ) + + # TODO remove this after Container class implementation is reliable + assert ( + bytes(eof_code).hex() + == bytes.fromhex("ef000101000402000100030400010000800001305000ef").hex() + ) + + eof_test( + data=eof_code, + expect_exception=None, + ) + + +@pytest.mark.xfail(reason="Evmone failing this test, need to fix!") +def test_EOF1V0016(eof_test: EOFTestFiller): + """ + Check that EOF1 with too many or too few bytes fails + """ + + eof_code = Container( + name="EOF1V0016", + sections=[ + Section.Code( + code=Op.ADDRESS + Op.POP + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data("0x0bad", custom_size=4), + ], + ) + + # TODO remove this after Container class implementation is reliable + assert ( + bytes(eof_code).hex() + == bytes.fromhex("ef0001010004020001000304000400008000013050000bad").hex() + ) + + eof_test( + data=eof_code, + expect_exception=EOFException.INVALID_SECTION_BODIES_SIZE, + ) + + +def test_EOF1I0006(eof_test: EOFTestFiller): + """ + Check that EOF1 with too many or too few bytes fails + """ + + eof_code = Container( + name="EOF1I0006", + sections=[ + Section.Code( + code=Op.ADDRESS + Op.POP + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data("0x0bad60A70BAD", custom_size=4), + ], + ) + + # TODO remove this after Container class implementation is reliable + assert ( + bytes(eof_code).hex() + == bytes.fromhex("ef0001010004020001000304000400008000013050000bad60A70BAD").hex() + ) + + eof_test( + data=eof_code, + expect_exception=EOFException.INVALID_SECTION_BODIES_SIZE, + ) + + +def test_EOF1V0001_2(eof_test: EOFTestFiller): + """ + Check that data section size is valid + """ + + eof_code = Container( + name="EOF1V0001", + sections=[ + Section.Code( + code=Op.ADDRESS + Op.POP + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data("0x0bad60A7"), + ], + ) + + # TODO remove this after Container class implementation is reliable + assert ( + bytes(eof_code).hex() + == bytes.fromhex("ef0001010004020001000304000400008000013050000bad60A7").hex() + ) + + eof_test( + data=eof_code, + expect_exception=None, + ) + + +def test_EOF1I0008(eof_test: EOFTestFiller): + """ + Check that EOF1 with an illegal opcode fails + """ + + eof_code = Container( + name="EOF1I0008", + sections=[ + Section.Code( + code=Op.ADDRESS + Opcode(0xEF) + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data("0x0bad60A7"), + ], + ) + + # TODO remove this after Container class implementation is reliable + assert ( + bytes(eof_code).hex() + == bytes.fromhex("ef00010100040200010003040004000080000130ef000bad60A7").hex() + ) + + eof_test( + data=eof_code, + expect_exception=EOFException.UNDEFINED_INSTRUCTION, + ) + + +def test_EOF1V0004(eof_test: EOFTestFiller): + """ + Check that valid EOF1 can include 0xFE, the designated invalid opcode + """ + + eof_code = Container( + name="EOF1V0004", + sections=[ + Section.Code( + code=Op.ADDRESS + Op.POP + Op.INVALID, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data("0x0bad60A7"), + ], + ) + + # TODO remove this after Container class implementation is reliable + assert ( + bytes(eof_code).hex() + == bytes.fromhex("ef0001010004020001000304000400008000013050fe0bad60A7").hex() + ) + + eof_test( + data=eof_code, + expect_exception=None, + ) + + +def test_EOF1I0005(eof_test: EOFTestFiller): + """ + Check that EOF1 with a bad end of sections number fails + """ + + eof_code = Container( + name="EOF1I0005", + sections=[ + Section.Code( + code=Op.ADDRESS + Op.POP + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data("0xef"), + ], + header_terminator=Bytes(b"\xFF"), + ) + + # TODO remove this after Container class implementation is reliable + assert ( + bytes(eof_code).hex() + == bytes.fromhex("ef00010100040200010003040001ff00800001305000ef").hex() + ) + + eof_test( + data=eof_code, + expect_exception=EOFException.MISSING_TERMINATOR, + ) + + +def test_EOF1V0008(eof_test: EOFTestFiller): + """ + Check that code that uses a new style relative jump (E0) succeeds + """ + + eof_code = Container( + name="EOF1V0008", + sections=[ + Section.Code( + code=Op.PUSH0 + Op.RJUMPI[3] + Op.RJUMP[3] + Op.RJUMP[3] + Op.RJUMP[-6] + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data("0x0bad60A7"), + ], + ) + + # TODO remove this after Container class implementation is reliable + assert ( + bytes(eof_code).hex() + == bytes.fromhex( + "ef0001010004020001000E04000400008000015FE10003E00003E00003E0FFFA000bad60A7" + ).hex() + ) + + eof_test( + data=eof_code, + expect_exception=None, + ) + + +def test_EOF1I0023(eof_test: EOFTestFiller): + """ + Sections with unreachable code fail + """ + + eof_code = Container( + name="EOF1I0023", + sections=[ + Section.Code( + code=Op.RJUMP[1] + Op.NOOP + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=0, + ), + Section.Data("0x0bad60A7"), + ], + ) + + # TODO remove this after Container class implementation is reliable + assert ( + bytes(eof_code).hex() + == bytes.fromhex("ef000101000402000100050400040000800000E000015B000bad60A7").hex() + ) + + eof_test( + data=eof_code, + expect_exception=EOFException.UNREACHABLE_INSTRUCTIONS, + ) + + +def test_EOF1V0011(eof_test: EOFTestFiller): + """ + Check that code that uses a new style conditional jump (5D) succeeds + """ + + eof_code = Container( + name="EOF1V0011", + sections=[ + Section.Code( + code=Op.PUSH1(1) + Op.RJUMPI[1] + Op.NOOP + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data("0x0bad60A7"), + ], + ) + + # TODO remove this after Container class implementation is reliable + assert ( + bytes(eof_code).hex() + == bytes.fromhex("ef0001010004020001000704000400008000016001E100015B000bad60A7").hex() + ) + + eof_test( + data=eof_code, + expect_exception=None, + ) + + +@pytest.mark.xfail(reason="Evmone failing this test, need to fix!") +def test_EOF1V0014(eof_test: EOFTestFiller): + """ + Sections that end with a legit terminating opcode are OK + """ + + eof_code = Container( + name="EOF1V0014", + sections=[ + Section.Code( + code=Op.PUSH0 + + Op.CALLDATALOAD + + Op.RJUMPV[0, 3, 6, 9] + + Op.JUMPF[1] + + Op.JUMPF[2] + + Op.JUMPF[3] + + Op.CALLF[4] + + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Code( + code=Op.PUSH0 + Op.PUSH0 + Op.RETURN, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ), + Section.Code( + code=Op.PUSH0 + Op.PUSH0 + Op.REVERT, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ), + Section.Code( + code=Op.INVALID, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=0, + ), + Section.Code( + code=Op.RETF, + code_inputs=0, + code_outputs=0, + max_stack_height=0, + ), + Section.Data("0x0bad60A7"), + ], + ) + + # TODO why ori expected this to have 3 args e2030000000300060009 ?? + # TODO JUMPF instruction was e5 but we have it b1 + assert str(bytes(Op.RJUMPV[0, 3, 6, 9]).hex()) == "e2040000000300060009" + + # TODO remove this after Container class implementation is reliable + assert ( + bytes(eof_code).hex() + == bytes.fromhex( + "EF0001010014020005001900030003000100010400040000800001008000020080000200800000000" + "000005f35e2040000000300060009b10001b10002b10003e30004005f5ff35f5ffdfee40bad60a7" + ).hex() + ) + + eof_test( + data=eof_code, + expect_exception=None, + ) + + +@pytest.mark.xfail(reason="Pyspec RJUMPV implementation could be off!") +def test_EOF1V0013(eof_test: EOFTestFiller): + """ + Jump tables work + """ + eof_code = Container( + name="EOF1V0013", + sections=[ + Section.Code( + code=Op.PUSH1(1) + + Op.RJUMPV[1, 2, 0] + + Op.ADDRESS + + Op.POP + + Op.ADDRESS + + Op.POP + + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data("0x0bad60A7"), + ], + ) + + # TODO why ori expected this to have 1 args e20100020000 and we make e202...?? + assert str(bytes(Op.RJUMPV[2, 0]).hex()) == "e20100020000" + + # TODO remove this after Container class implementation is reliable + assert ( + bytes(eof_code).hex() + == bytes.fromhex( + "ef0001010004020001000D04000400008000016001E2010002000030503050000bad60A7" + ).hex() + ) + + eof_test( + data=eof_code, + expect_exception=None, + ) + + +def test_EOF1I0019(eof_test: EOFTestFiller): + """ + Check that jumps into the middle on an opcode are not allowed + """ + eof_code = Container( + name="EOF1I0019", + sections=[ + Section.Code( + code=Op.PUSH1(1) + + Op.RJUMPV[2, -1] + + Op.ADDRESS + + Op.POP + + Op.ADDRESS + + Op.POP + + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data("0x0bad60A7"), + ], + ) + + assert str(bytes(Op.RJUMPV[2, -1]).hex()) == "e2020002ffff" + + # TODO remove this after Container class implementation is reliable + assert ( + bytes(eof_code).hex() + == bytes.fromhex( + "ef0001010004020001000D04000400008000016001E2020002FFFF30503050000bad60A7" + ).hex() + ) + + eof_test( + data=eof_code, + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +def test_EOF1I0020(eof_test: EOFTestFiller): + """ + Check that you can't get to the same opcode with two different stack heights + """ + eof_code = Container( + name="EOF1I0020", + sections=[ + Section.Code( + code=Op.PUSH1(1) + Op.RJUMPI[1] + Op.ADDRESS + Op.NOOP + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data("0x0bad60A7"), + ], + ) + + # TODO remove this after Container class implementation is reliable + assert ( + bytes(eof_code).hex() + == bytes.fromhex("ef0001010004020001000804000400008000016001E10001305B000bad60A7").hex() + ) + + eof_test( + data=eof_code, + expect_exception=None, + ) From 0786794aa8a12656add601e019b25181dd96d07b Mon Sep 17 00:00:00 2001 From: Dimitry Kh Date: Fri, 3 May 2024 11:48:40 +0200 Subject: [PATCH 3/9] fix eof opcodes implementation rjumpv and jumpf --- src/ethereum_test_tools/vm/opcode.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ethereum_test_tools/vm/opcode.py b/src/ethereum_test_tools/vm/opcode.py index 8b503670c3..1b6c78e276 100644 --- a/src/ethereum_test_tools/vm/opcode.py +++ b/src/ethereum_test_tools/vm/opcode.py @@ -382,14 +382,14 @@ def _rjumpv_encoder(*args: int | bytes | Iterable[int]) -> bytes: elif isinstance(args[0], Iterable): int_args = list(args[0]) return b"".join( - [len(int_args).to_bytes(RJUMPV_MAX_INDEX_BYTE_LENGTH, "big")] + [(len(int_args) - 1).to_bytes(RJUMPV_MAX_INDEX_BYTE_LENGTH, "big")] + [ i.to_bytes(RJUMPV_BRANCH_OFFSET_BYTE_LENGTH, "big", signed=True) for i in int_args ] ) return b"".join( - [len(args).to_bytes(RJUMPV_MAX_INDEX_BYTE_LENGTH, "big")] + [(len(args) - 1).to_bytes(RJUMPV_MAX_INDEX_BYTE_LENGTH, "big")] + [ i.to_bytes(RJUMPV_BRANCH_OFFSET_BYTE_LENGTH, "big", signed=True) for i in args @@ -4932,7 +4932,7 @@ class Opcodes(Opcode, Enum): 3 """ - JUMPF = Opcode(0xB1, data_portion_length=2) + JUMPF = Opcode(0xE5, data_portion_length=2) """ !!! Note: This opcode is under development From 5129843bae6efd96fb01a7f3a6e811cd87560eb9 Mon Sep 17 00:00:00 2001 From: Dimitry Kh Date: Fri, 3 May 2024 11:49:10 +0200 Subject: [PATCH 4/9] add a few more tweaks to eof Container class --- src/ethereum_test_tools/eof/v1/__init__.py | 46 ++++++++++++++++++++-- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/src/ethereum_test_tools/eof/v1/__init__.py b/src/ethereum_test_tools/eof/v1/__init__.py index c9cc85ecd3..e54a72d31e 100644 --- a/src/ethereum_test_tools/eof/v1/__init__.py +++ b/src/ethereum_test_tools/eof/v1/__init__.py @@ -121,6 +121,22 @@ class Section(CopyValidateModel): Whether to automatically compute the best suggestion for the code_inputs, code_outputs values for this code section. """ + skip_header_listing: bool = False + """ + Skip section from listing in the header + """ + skip_body_listing: bool = False + """ + Skip section from listing in the body + """ + skip_types_body_listing: bool = False + """ + Skip section from listing in the types body (input, output, stack) bytes + """ + skip_types_header_listing: bool = False + """ + Skip section from listing in the types header (not calculating input, output, stack size) + """ @cached_property def header(self) -> bytes: @@ -197,8 +213,18 @@ def list_header(sections: List["Section"]) -> bytes: return b"".join(s.header for s in sections) h = sections[0].kind.to_bytes(HEADER_SECTION_KIND_BYTE_LENGTH, "big") - h += len(sections).to_bytes(HEADER_SECTION_COUNT_BYTE_LENGTH, "big") + + # Count only those sections that are not marked to be skipped for header calculation + header_registered_sections = 0 + for cs in sections: + if not cs.skip_header_listing: + header_registered_sections += 1 + + h += header_registered_sections.to_bytes(HEADER_SECTION_COUNT_BYTE_LENGTH, "big") for cs in sections: + # If section is marked to skip the header calculation, don't make header for it + if cs.skip_header_listing: + continue size = cs.custom_size if "custom_size" in cs.model_fields_set else len(cs.data) h += size.to_bytes(HEADER_SECTION_SIZE_BYTE_LENGTH, "big") @@ -333,8 +359,20 @@ def bytecode(self) -> bytes: # Add type section if needed if self.auto_type_section.any() and count_sections(sections, SectionKind.TYPE) == 0: - type_section_data = b"".join(s.type_definition for s in sections) - sections = [Section(kind=SectionKind.TYPE, data=type_section_data)] + sections + # Calculate skipping flags + types_header_size = 0 + type_section_data = b"" + for s in sections: + types_header_size += ( + len(s.type_definition) if not s.skip_types_header_listing else 0 + ) + type_section_data += s.type_definition if not s.skip_types_body_listing else b"" + + sections = [ + Section( + kind=SectionKind.TYPE, data=type_section_data, custom_size=types_header_size + ) + ] + sections # Add data section if needed if self.auto_data_section and count_sections(sections, SectionKind.DATA) == 0: @@ -371,7 +409,7 @@ def bytecode(self) -> bytes: for s in body_sections: if s.kind == SectionKind.TYPE and self.auto_type_section == AutoSection.ONLY_HEADER: continue - if s.data: + if s.data and not s.skip_body_listing: c += s.data # Add extra (garbage) From 8484d46481348cd74401f86138a25b2f4e98e160 Mon Sep 17 00:00:00 2001 From: Dimitry Kh Date: Fri, 3 May 2024 11:49:33 +0200 Subject: [PATCH 5/9] add new EOF exceptions --- .../exceptions/evmone_exceptions.py | 3 ++- src/ethereum_test_tools/exceptions/exceptions.py | 6 +++++- tests/prague/eip3540_eof_v1/container.py | 12 ++++++------ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/ethereum_test_tools/exceptions/evmone_exceptions.py b/src/ethereum_test_tools/exceptions/evmone_exceptions.py index dc074b1c01..754af7681d 100644 --- a/src/ethereum_test_tools/exceptions/evmone_exceptions.py +++ b/src/ethereum_test_tools/exceptions/evmone_exceptions.py @@ -40,7 +40,7 @@ class EvmoneExceptionMapper: ExceptionMessage( EOFException.INVALID_SECTION_BODIES_SIZE, "err: invalid_section_bodies_size" ), - ExceptionMessage(EOFException.INVALID_TYPE_SIZE, "err: invalid_type_section_size"), + ExceptionMessage(EOFException.INVALID_TYPE_SECTION_SIZE, "err: invalid_type_section_size"), ExceptionMessage(EOFException.INCOMPLETE_SECTION_SIZE, "err: incomplete_section_size"), ExceptionMessage(EOFException.INCOMPLETE_SECTION_NUMBER, "err: incomplete_section_number"), ExceptionMessage(EOFException.TOO_MANY_CODE_SECTIONS, "err: too_many_code_sections"), @@ -48,6 +48,7 @@ class EvmoneExceptionMapper: ExceptionMessage(EOFException.UNDEFINED_INSTRUCTION, "err: undefined_instruction"), ExceptionMessage(EOFException.UNREACHABLE_INSTRUCTIONS, "err: unreachable_instructions"), ExceptionMessage(EOFException.INVALID_RJUMP_DESTINATION, "err: invalid_rjump_destination"), + ExceptionMessage(EOFException.UNREACHABLE_CODE_SECTIONS, "err: unreachable_code_sections"), ) def __init__(self) -> None: diff --git a/src/ethereum_test_tools/exceptions/exceptions.py b/src/ethereum_test_tools/exceptions/exceptions.py index 6aadcf2e56..5e3c516fa6 100644 --- a/src/ethereum_test_tools/exceptions/exceptions.py +++ b/src/ethereum_test_tools/exceptions/exceptions.py @@ -227,7 +227,7 @@ class EOFException(ExceptionBase): """ EOF container missing types section """ - INVALID_TYPE_SIZE = auto() + INVALID_TYPE_SECTION_SIZE = auto() """ EOF container types section has wrong size """ @@ -299,6 +299,10 @@ class EOFException(ExceptionBase): """ EOF container's code have instructions that are unreachable """ + UNREACHABLE_CODE_SECTIONS = auto() + """ + EOF container's body have code sections that are unreachable + """ """ diff --git a/tests/prague/eip3540_eof_v1/container.py b/tests/prague/eip3540_eof_v1/container.py index e2af240148..eef7913628 100644 --- a/tests/prague/eip3540_eof_v1/container.py +++ b/tests/prague/eip3540_eof_v1/container.py @@ -425,7 +425,7 @@ Section.Code(Op.STOP), ], auto_type_section=AutoSection.NONE, - # TODO the exception must be about type section EOFException.INVALID_TYPE_SIZE, + # TODO the exception must be about type section EOFException.INVALID_TYPE_SECTION_SIZE, validity_error=EOFException.ZERO_SECTION_SIZE, ), Container( @@ -435,7 +435,7 @@ Section.Code(Op.STOP), ], auto_type_section=AutoSection.NONE, - validity_error=EOFException.INVALID_TYPE_SIZE, + validity_error=EOFException.INVALID_TYPE_SECTION_SIZE, ), Container( name="type_section_too_small_2", @@ -444,7 +444,7 @@ Section.Code(Op.STOP), ], auto_type_section=AutoSection.NONE, - validity_error=EOFException.INVALID_TYPE_SIZE, + validity_error=EOFException.INVALID_TYPE_SECTION_SIZE, ), Container( name="type_section_too_big", @@ -453,7 +453,7 @@ Section.Code(Op.STOP), ], auto_type_section=AutoSection.NONE, - validity_error=EOFException.INVALID_TYPE_SIZE, + validity_error=EOFException.INVALID_TYPE_SECTION_SIZE, ), ] @@ -593,7 +593,7 @@ Section.Data(data="0x00", force_type_listing=True), Section.Code(Op.STOP), ], - validity_error=EOFException.INVALID_TYPE_SIZE, + validity_error=EOFException.INVALID_TYPE_SECTION_SIZE, ), Container( name="code_sections_above_1024", @@ -607,7 +607,7 @@ Section.Code(Op.STOP), ], auto_type_section=AutoSection.NONE, - validity_error=EOFException.INVALID_TYPE_SIZE, + validity_error=EOFException.INVALID_TYPE_SECTION_SIZE, ), Container( name="single_code_section_incomplete_type_2", From 6a2b7590279190367b08602dd30efebed1a0ec1a Mon Sep 17 00:00:00 2001 From: Dimitry Kh Date: Fri, 3 May 2024 12:02:50 +0200 Subject: [PATCH 6/9] add a few efExample ori tests --- .../test_example_validInvalid.py | 499 ----------------- .../test_example_valid_invalid.py | 517 ++++++++++++++++++ 2 files changed, 517 insertions(+), 499 deletions(-) delete mode 100644 tests/prague/eip3540_eof_v1/test_example_validInvalid.py create mode 100644 tests/prague/eip3540_eof_v1/test_example_valid_invalid.py diff --git a/tests/prague/eip3540_eof_v1/test_example_validInvalid.py b/tests/prague/eip3540_eof_v1/test_example_validInvalid.py deleted file mode 100644 index 0de7ef5755..0000000000 --- a/tests/prague/eip3540_eof_v1/test_example_validInvalid.py +++ /dev/null @@ -1,499 +0,0 @@ -""" -EOF Classes example use -""" - -import pytest - -from ethereum_test_tools import EOFTestFiller, Opcode -from ethereum_test_tools import Opcodes as Op -from ethereum_test_tools.eof.v1 import Bytes, Container, EOFException, Section -from ethereum_test_tools.eof.v1.constants import NON_RETURNING_SECTION - -from .spec import EOF_FORK_NAME - -REFERENCE_SPEC_GIT_PATH = "EIPS/eip-3540.md" -REFERENCE_SPEC_VERSION = "8dcb0a8c1c0102c87224308028632cc986a61183" - -pytestmark = pytest.mark.valid_from(EOF_FORK_NAME) - - -def test_EOF1V0001(eof_test: EOFTestFiller): - """ - Check that simple EOF1 deploys - """ - - eof_code = Container( - name="EOF1V0001", - sections=[ - Section.Code( - code=Op.ADDRESS + Op.POP + Op.STOP, - code_inputs=0, - code_outputs=NON_RETURNING_SECTION, - max_stack_height=1, - ), - Section.Data("0xef"), - ], - ) - - # TODO remove this after Container class implementation is reliable - assert ( - bytes(eof_code).hex() - == bytes.fromhex("ef000101000402000100030400010000800001305000ef").hex() - ) - - eof_test( - data=eof_code, - expect_exception=None, - ) - - -@pytest.mark.xfail(reason="Evmone failing this test, need to fix!") -def test_EOF1V0016(eof_test: EOFTestFiller): - """ - Check that EOF1 with too many or too few bytes fails - """ - - eof_code = Container( - name="EOF1V0016", - sections=[ - Section.Code( - code=Op.ADDRESS + Op.POP + Op.STOP, - code_inputs=0, - code_outputs=NON_RETURNING_SECTION, - max_stack_height=1, - ), - Section.Data("0x0bad", custom_size=4), - ], - ) - - # TODO remove this after Container class implementation is reliable - assert ( - bytes(eof_code).hex() - == bytes.fromhex("ef0001010004020001000304000400008000013050000bad").hex() - ) - - eof_test( - data=eof_code, - expect_exception=EOFException.INVALID_SECTION_BODIES_SIZE, - ) - - -def test_EOF1I0006(eof_test: EOFTestFiller): - """ - Check that EOF1 with too many or too few bytes fails - """ - - eof_code = Container( - name="EOF1I0006", - sections=[ - Section.Code( - code=Op.ADDRESS + Op.POP + Op.STOP, - code_inputs=0, - code_outputs=NON_RETURNING_SECTION, - max_stack_height=1, - ), - Section.Data("0x0bad60A70BAD", custom_size=4), - ], - ) - - # TODO remove this after Container class implementation is reliable - assert ( - bytes(eof_code).hex() - == bytes.fromhex("ef0001010004020001000304000400008000013050000bad60A70BAD").hex() - ) - - eof_test( - data=eof_code, - expect_exception=EOFException.INVALID_SECTION_BODIES_SIZE, - ) - - -def test_EOF1V0001_2(eof_test: EOFTestFiller): - """ - Check that data section size is valid - """ - - eof_code = Container( - name="EOF1V0001", - sections=[ - Section.Code( - code=Op.ADDRESS + Op.POP + Op.STOP, - code_inputs=0, - code_outputs=NON_RETURNING_SECTION, - max_stack_height=1, - ), - Section.Data("0x0bad60A7"), - ], - ) - - # TODO remove this after Container class implementation is reliable - assert ( - bytes(eof_code).hex() - == bytes.fromhex("ef0001010004020001000304000400008000013050000bad60A7").hex() - ) - - eof_test( - data=eof_code, - expect_exception=None, - ) - - -def test_EOF1I0008(eof_test: EOFTestFiller): - """ - Check that EOF1 with an illegal opcode fails - """ - - eof_code = Container( - name="EOF1I0008", - sections=[ - Section.Code( - code=Op.ADDRESS + Opcode(0xEF) + Op.STOP, - code_inputs=0, - code_outputs=NON_RETURNING_SECTION, - max_stack_height=1, - ), - Section.Data("0x0bad60A7"), - ], - ) - - # TODO remove this after Container class implementation is reliable - assert ( - bytes(eof_code).hex() - == bytes.fromhex("ef00010100040200010003040004000080000130ef000bad60A7").hex() - ) - - eof_test( - data=eof_code, - expect_exception=EOFException.UNDEFINED_INSTRUCTION, - ) - - -def test_EOF1V0004(eof_test: EOFTestFiller): - """ - Check that valid EOF1 can include 0xFE, the designated invalid opcode - """ - - eof_code = Container( - name="EOF1V0004", - sections=[ - Section.Code( - code=Op.ADDRESS + Op.POP + Op.INVALID, - code_inputs=0, - code_outputs=NON_RETURNING_SECTION, - max_stack_height=1, - ), - Section.Data("0x0bad60A7"), - ], - ) - - # TODO remove this after Container class implementation is reliable - assert ( - bytes(eof_code).hex() - == bytes.fromhex("ef0001010004020001000304000400008000013050fe0bad60A7").hex() - ) - - eof_test( - data=eof_code, - expect_exception=None, - ) - - -def test_EOF1I0005(eof_test: EOFTestFiller): - """ - Check that EOF1 with a bad end of sections number fails - """ - - eof_code = Container( - name="EOF1I0005", - sections=[ - Section.Code( - code=Op.ADDRESS + Op.POP + Op.STOP, - code_inputs=0, - code_outputs=NON_RETURNING_SECTION, - max_stack_height=1, - ), - Section.Data("0xef"), - ], - header_terminator=Bytes(b"\xFF"), - ) - - # TODO remove this after Container class implementation is reliable - assert ( - bytes(eof_code).hex() - == bytes.fromhex("ef00010100040200010003040001ff00800001305000ef").hex() - ) - - eof_test( - data=eof_code, - expect_exception=EOFException.MISSING_TERMINATOR, - ) - - -def test_EOF1V0008(eof_test: EOFTestFiller): - """ - Check that code that uses a new style relative jump (E0) succeeds - """ - - eof_code = Container( - name="EOF1V0008", - sections=[ - Section.Code( - code=Op.PUSH0 + Op.RJUMPI[3] + Op.RJUMP[3] + Op.RJUMP[3] + Op.RJUMP[-6] + Op.STOP, - code_inputs=0, - code_outputs=NON_RETURNING_SECTION, - max_stack_height=1, - ), - Section.Data("0x0bad60A7"), - ], - ) - - # TODO remove this after Container class implementation is reliable - assert ( - bytes(eof_code).hex() - == bytes.fromhex( - "ef0001010004020001000E04000400008000015FE10003E00003E00003E0FFFA000bad60A7" - ).hex() - ) - - eof_test( - data=eof_code, - expect_exception=None, - ) - - -def test_EOF1I0023(eof_test: EOFTestFiller): - """ - Sections with unreachable code fail - """ - - eof_code = Container( - name="EOF1I0023", - sections=[ - Section.Code( - code=Op.RJUMP[1] + Op.NOOP + Op.STOP, - code_inputs=0, - code_outputs=NON_RETURNING_SECTION, - max_stack_height=0, - ), - Section.Data("0x0bad60A7"), - ], - ) - - # TODO remove this after Container class implementation is reliable - assert ( - bytes(eof_code).hex() - == bytes.fromhex("ef000101000402000100050400040000800000E000015B000bad60A7").hex() - ) - - eof_test( - data=eof_code, - expect_exception=EOFException.UNREACHABLE_INSTRUCTIONS, - ) - - -def test_EOF1V0011(eof_test: EOFTestFiller): - """ - Check that code that uses a new style conditional jump (5D) succeeds - """ - - eof_code = Container( - name="EOF1V0011", - sections=[ - Section.Code( - code=Op.PUSH1(1) + Op.RJUMPI[1] + Op.NOOP + Op.STOP, - code_inputs=0, - code_outputs=NON_RETURNING_SECTION, - max_stack_height=1, - ), - Section.Data("0x0bad60A7"), - ], - ) - - # TODO remove this after Container class implementation is reliable - assert ( - bytes(eof_code).hex() - == bytes.fromhex("ef0001010004020001000704000400008000016001E100015B000bad60A7").hex() - ) - - eof_test( - data=eof_code, - expect_exception=None, - ) - - -@pytest.mark.xfail(reason="Evmone failing this test, need to fix!") -def test_EOF1V0014(eof_test: EOFTestFiller): - """ - Sections that end with a legit terminating opcode are OK - """ - - eof_code = Container( - name="EOF1V0014", - sections=[ - Section.Code( - code=Op.PUSH0 - + Op.CALLDATALOAD - + Op.RJUMPV[0, 3, 6, 9] - + Op.JUMPF[1] - + Op.JUMPF[2] - + Op.JUMPF[3] - + Op.CALLF[4] - + Op.STOP, - code_inputs=0, - code_outputs=NON_RETURNING_SECTION, - max_stack_height=1, - ), - Section.Code( - code=Op.PUSH0 + Op.PUSH0 + Op.RETURN, - code_inputs=0, - code_outputs=NON_RETURNING_SECTION, - max_stack_height=2, - ), - Section.Code( - code=Op.PUSH0 + Op.PUSH0 + Op.REVERT, - code_inputs=0, - code_outputs=NON_RETURNING_SECTION, - max_stack_height=2, - ), - Section.Code( - code=Op.INVALID, - code_inputs=0, - code_outputs=NON_RETURNING_SECTION, - max_stack_height=0, - ), - Section.Code( - code=Op.RETF, - code_inputs=0, - code_outputs=0, - max_stack_height=0, - ), - Section.Data("0x0bad60A7"), - ], - ) - - # TODO why ori expected this to have 3 args e2030000000300060009 ?? - # TODO JUMPF instruction was e5 but we have it b1 - assert str(bytes(Op.RJUMPV[0, 3, 6, 9]).hex()) == "e2040000000300060009" - - # TODO remove this after Container class implementation is reliable - assert ( - bytes(eof_code).hex() - == bytes.fromhex( - "EF0001010014020005001900030003000100010400040000800001008000020080000200800000000" - "000005f35e2040000000300060009b10001b10002b10003e30004005f5ff35f5ffdfee40bad60a7" - ).hex() - ) - - eof_test( - data=eof_code, - expect_exception=None, - ) - - -@pytest.mark.xfail(reason="Pyspec RJUMPV implementation could be off!") -def test_EOF1V0013(eof_test: EOFTestFiller): - """ - Jump tables work - """ - eof_code = Container( - name="EOF1V0013", - sections=[ - Section.Code( - code=Op.PUSH1(1) - + Op.RJUMPV[1, 2, 0] - + Op.ADDRESS - + Op.POP - + Op.ADDRESS - + Op.POP - + Op.STOP, - code_inputs=0, - code_outputs=NON_RETURNING_SECTION, - max_stack_height=1, - ), - Section.Data("0x0bad60A7"), - ], - ) - - # TODO why ori expected this to have 1 args e20100020000 and we make e202...?? - assert str(bytes(Op.RJUMPV[2, 0]).hex()) == "e20100020000" - - # TODO remove this after Container class implementation is reliable - assert ( - bytes(eof_code).hex() - == bytes.fromhex( - "ef0001010004020001000D04000400008000016001E2010002000030503050000bad60A7" - ).hex() - ) - - eof_test( - data=eof_code, - expect_exception=None, - ) - - -def test_EOF1I0019(eof_test: EOFTestFiller): - """ - Check that jumps into the middle on an opcode are not allowed - """ - eof_code = Container( - name="EOF1I0019", - sections=[ - Section.Code( - code=Op.PUSH1(1) - + Op.RJUMPV[2, -1] - + Op.ADDRESS - + Op.POP - + Op.ADDRESS - + Op.POP - + Op.STOP, - code_inputs=0, - code_outputs=NON_RETURNING_SECTION, - max_stack_height=1, - ), - Section.Data("0x0bad60A7"), - ], - ) - - assert str(bytes(Op.RJUMPV[2, -1]).hex()) == "e2020002ffff" - - # TODO remove this after Container class implementation is reliable - assert ( - bytes(eof_code).hex() - == bytes.fromhex( - "ef0001010004020001000D04000400008000016001E2020002FFFF30503050000bad60A7" - ).hex() - ) - - eof_test( - data=eof_code, - expect_exception=EOFException.INVALID_RJUMP_DESTINATION, - ) - - -def test_EOF1I0020(eof_test: EOFTestFiller): - """ - Check that you can't get to the same opcode with two different stack heights - """ - eof_code = Container( - name="EOF1I0020", - sections=[ - Section.Code( - code=Op.PUSH1(1) + Op.RJUMPI[1] + Op.ADDRESS + Op.NOOP + Op.STOP, - code_inputs=0, - code_outputs=NON_RETURNING_SECTION, - max_stack_height=1, - ), - Section.Data("0x0bad60A7"), - ], - ) - - # TODO remove this after Container class implementation is reliable - assert ( - bytes(eof_code).hex() - == bytes.fromhex("ef0001010004020001000804000400008000016001E10001305B000bad60A7").hex() - ) - - eof_test( - data=eof_code, - expect_exception=None, - ) diff --git a/tests/prague/eip3540_eof_v1/test_example_valid_invalid.py b/tests/prague/eip3540_eof_v1/test_example_valid_invalid.py new file mode 100644 index 0000000000..741b9905a0 --- /dev/null +++ b/tests/prague/eip3540_eof_v1/test_example_valid_invalid.py @@ -0,0 +1,517 @@ +""" +EOF Classes example use +""" + +import pytest + +from ethereum_test_tools import EOFTestFiller, Opcode +from ethereum_test_tools import Opcodes as Op +from ethereum_test_tools.eof.v1 import Bytes, Container, EOFException, Section +from ethereum_test_tools.eof.v1.constants import NON_RETURNING_SECTION + +from .spec import EOF_FORK_NAME + +REFERENCE_SPEC_GIT_PATH = "EIPS/eip-3540.md" +REFERENCE_SPEC_VERSION = "8dcb0a8c1c0102c87224308028632cc986a61183" + +pytestmark = pytest.mark.valid_from(EOF_FORK_NAME) + + +@pytest.mark.parametrize( + "eof_code,expected_hex_bytecode,exception", + [ + pytest.param( + # Check that simple EOF1 deploys + Container( + name="EOF1V0001", + sections=[ + Section.Code( + code=Op.ADDRESS + Op.POP + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data("0xef"), + ], + ), + "ef000101000402000100030400010000800001305000ef", + None, + id="simple_eof_1_deploy", + ), + pytest.param( + # Check that EOF1 undersize data is ok (4 declared, 2 provided) + # https://github.com/ipsilon/eof/blob/main/spec/eof.md#data-section-lifecycle + Container( + name="EOF1V0016", + sections=[ + Section.Code( + code=Op.ADDRESS + Op.POP + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data("0x0bad", custom_size=4), + ], + ), + "ef0001010004020001000304000400008000013050000bad", + None, + id="undersize_data_ok", + ), + pytest.param( + # Check that EOF1 with too many or too few bytes fails + Container( + name="EOF1I0006", + sections=[ + Section.Code( + code=Op.ADDRESS + Op.POP + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data("0x0bad60A70BAD", custom_size=4), + ], + ), + "ef0001010004020001000304000400008000013050000bad60A70BAD", + EOFException.INVALID_SECTION_BODIES_SIZE, + id="oversize_data_fail", + ), + pytest.param( + # Check that data section size is valid + Container( + name="EOF1V0001", + sections=[ + Section.Code( + code=Op.ADDRESS + Op.POP + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data("0x0bad60A7"), + ], + ), + "ef0001010004020001000304000400008000013050000bad60A7", + None, + id="data_ok", + ), + pytest.param( + # Check that EOF1 with an illegal opcode fails + Container( + name="EOF1I0008", + sections=[ + Section.Code( + code=Op.ADDRESS + Opcode(0xEF) + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data("0x0bad60A7"), + ], + ), + "ef00010100040200010003040004000080000130ef000bad60A7", + EOFException.UNDEFINED_INSTRUCTION, + id="illegal_opcode_fail", + ), + pytest.param( + # Check that valid EOF1 can include 0xFE, the designated invalid opcode + Container( + name="EOF1V0004", + sections=[ + Section.Code( + code=Op.ADDRESS + Op.POP + Op.INVALID, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data("0x0bad60A7"), + ], + ), + "ef0001010004020001000304000400008000013050fe0bad60A7", + None, + id="fe_opcode_ok", + ), + pytest.param( + # Check that EOF1 with a bad end of sections number fails + Container( + name="EOF1I0005", + sections=[ + Section.Code( + code=Op.ADDRESS + Op.POP + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data("0xef"), + ], + header_terminator=Bytes(b"\xFF"), + ), + "ef00010100040200010003040001ff00800001305000ef", + EOFException.MISSING_TERMINATOR, + id="headers_terminator_invalid", + ), + pytest.param( + # Check that code that uses a new style relative jump succeeds + Container( + name="EOF1V0008", + sections=[ + Section.Code( + code=Op.PUSH0 + + Op.RJUMPI[3] + + Op.RJUMP[3] + + Op.RJUMP[3] + + Op.RJUMP[-6] + + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data("0x0bad60A7"), + ], + ), + "ef0001010004020001000E04000400008000015FE10003E00003E00003E0FFFA000bad60A7", + None, + id="rjump_valid", + ), + pytest.param( + # Sections with unreachable code fail + Container( + name="EOF1I0023", + sections=[ + Section.Code( + code=Op.RJUMP[1] + Op.NOOP + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=0, + ), + Section.Data("0x0bad60A7"), + ], + ), + "ef000101000402000100050400040000800000E000015B000bad60A7", + EOFException.UNREACHABLE_INSTRUCTIONS, + id="unreachable_code", + ), + pytest.param( + # Check that code that uses a new style conditional jump succeeds + Container( + name="EOF1V0011", + sections=[ + Section.Code( + code=Op.PUSH1(1) + Op.RJUMPI[1] + Op.NOOP + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data("0x0bad60A7"), + ], + ), + "ef0001010004020001000704000400008000016001E100015B000bad60A7", + None, + id="rjumpi_valid", + ), + pytest.param( + # Sections that end with a legit terminating opcode are OK + Container( + name="EOF1V0014", + sections=[ + Section.Code( + code=Op.PUSH0 + + Op.CALLDATALOAD + + Op.RJUMPV[0, 3, 6, 9] + + Op.JUMPF[1] + + Op.JUMPF[2] + + Op.JUMPF[3] + + Op.CALLF[4] + + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Code( + code=Op.PUSH0 + Op.PUSH0 + Op.RETURN, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ), + Section.Code( + code=Op.PUSH0 + Op.PUSH0 + Op.REVERT, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ), + Section.Code( + code=Op.INVALID, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=0, + ), + Section.Code( + code=Op.RETF, + code_inputs=0, + code_outputs=0, + max_stack_height=0, + ), + Section.Data("0x0bad60A7"), + ], + ), + "EF0001010014020005001900030003000100010400040000800001008000020080000200800000000" + "000005f35e2030000000300060009e50001e50002e50003e30004005f5ff35f5ffdfee40bad60a7", + None, + id="rjumpv_section_terminator_valid", + ), + pytest.param( + # Check that jump tables work + Container( + name="EOF1V0013", + sections=[ + Section.Code( + code=Op.PUSH1(1) + + Op.RJUMPV[2, 0] + + Op.ADDRESS + + Op.POP + + Op.ADDRESS + + Op.POP + + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data("0x0bad60A7"), + ], + ), + "ef0001010004020001000D04000400008000016001E2010002000030503050000bad60A7", + None, + id="jump_tables_valid", + ), + pytest.param( + # Check that jumps into the middle on an opcode are not allowed + Container( + name="EOF1I0019", + sections=[ + Section.Code( + code=Op.PUSH1(1) + + Op.RJUMPV[b"\x02\x00\x02\xFF\xFF"] + + Op.ADDRESS + + Op.POP + + Op.ADDRESS + + Op.POP + + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data("0x0bad60A7"), + ], + ), + "ef0001010004020001000D04000400008000016001E2020002FFFF30503050000bad60A7", + EOFException.INVALID_RJUMP_DESTINATION, + id="rjump_invalid", + ), + pytest.param( + # TODO why here is expected an exception by the comment but test is valid + # Check that you can't get to the same opcode with two different stack heights + Container( + name="EOF1I0020", + sections=[ + Section.Code( + code=Op.PUSH1(1) + Op.RJUMPI[1] + Op.ADDRESS + Op.NOOP + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data("0x0bad60A7"), + ], + ), + "ef0001010004020001000804000400008000016001E10001305B000bad60A7", + None, + id="jump_to_opcode_ok", + ), + pytest.param( + # Check that jumps into the middle on an opcode are not allowed + Container( + name="EOF1I0019", + sections=[ + Section.Code( + code=Op.RJUMP[3] + Op.RJUMP[2] + Op.RJUMP[-6] + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=0, + ), + Section.Data("0x0bad60A7"), + ], + ), + "ef0001010004020001000A0400040000800000E00003E00002E0FFFA000bad60A7", + EOFException.INVALID_RJUMP_DESTINATION, + id="rjump_3_2_m6_fails", + ), + pytest.param( + # Check that jumps into the middle on an opcode are not allowed + Container( + name="EOF1I0019", + sections=[ + Section.Code( + code=Op.PUSH1(0) + + Op.PUSH1(0) + + Op.PUSH1(0) + + Op.RJUMPI[3] + + Op.RJUMPI[2] + + Op.RJUMPI[-6] + + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=3, + ), + Section.Data("0x0bad60A7"), + ], + ), + "ef000101000402000100100400040000800003600060006000E10003E10002E1FFFA000bad60A7", + EOFException.INVALID_RJUMP_DESTINATION, + id="push1_0_0_0_rjump_3_2_m6_fails", + ), + pytest.param( + # Check that that code that uses removed opcodes fails + Container( + name="EOF1I0015", + sections=[ + Section.Code( + code=Op.PUSH1(3) + Op.JUMP + Op.JUMPDEST + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data("0xef"), + ], + ), + "ef0001010004020001000504000100008000016003565B00ef", + EOFException.UNDEFINED_INSTRUCTION, + id="jump_jumpdest_fails", + ), + ], +) +def test_example_valid_invalid( + eof_test: EOFTestFiller, + eof_code: Container, + expected_hex_bytecode: str, + exception: EOFException, +): + """ + Verify eof container construction and exception + """ + # TODO remove this after Container class implementation is reliable + assert bytes(eof_code).hex() == bytes.fromhex(expected_hex_bytecode).hex() + + eof_test( + data=eof_code, + expect_exception=exception, + ) + + +def test_new_eof_opcodes(eof_test: EOFTestFiller): + """ + Assert the compilation of new eof opcodes is valid + """ + assert str(bytes(Op.RJUMPV[0, 3, 6, 9]).hex()) == "e2030000000300060009" + assert str(bytes(Op.RJUMPV[2, 0]).hex()) == "e20100020000" + assert str(bytes(Op.RJUMPV[b"\x02\x00\x02\xFF\xFF"]).hex()) == "e2020002ffff" + + +@pytest.mark.parametrize( + "skip_header_listing, skip_body_listing, skip_types_body_listing, skip_types_header_listing," + "expected_code, expected_exception", + [ + ( + # Data 16 test case of valid invalid eof ori filler + True, # second section is not in code header array + True, # second section is not in container's body (it's code bytes) + False, # but it's code input bytes still listed in container's body + False, # but it's code input bytes size still added to types section size + "ef000101000802000100030400040000800001000000003050000bad60A7", + EOFException.INVALID_TYPE_SECTION_SIZE, + ), + ( + True, # second section is not in code header array + False, # second section code is in container's body (3050000) + False, # but it's code input bytes still listed in container's body + False, # but it's code input bytes size still added to types section size + "ef000101000802000100030400040000800001000000003050003050000bad60A7", + EOFException.INVALID_SECTION_BODIES_SIZE, + ), + ( + False, # second section is mentioned in code header array (0003) + True, # second section is not in container's body (it's code bytes) + False, # but it's code input bytes still listed in container's body + False, # but it's code input bytes size still added to types section size + "ef0001010008020002000300030400040000800001000000003050000bad60A7", + EOFException.UNREACHABLE_CODE_SECTIONS, + ), + ( + False, # second section is mentioned in code header array (0003) + False, # second section code is in container's body (3050000) + False, # but it's code input bytes still listed in container's body + False, # but it's code input bytes size still added to types section size + "ef0001010008020002000300030400040000800001000000003050003050000bad60A7", + EOFException.UNREACHABLE_CODE_SECTIONS, + ), + ( + # Data 17 test case of valid invalid eof ori filler + True, # second section is not in code header array + True, # second section is not in container's body (it's code bytes) + True, # it's code input bytes are not listed in container's body (00000000) + False, # but it's code input bytes size still added to types section size + "ef0001010008020001000304000400008000013050000bad60a7", + EOFException.INVALID_TYPE_SECTION_SIZE, + ), + ( + True, # second section is not in code header array + True, # second section is not in container's body (it's code bytes) + True, # it's code input bytes are not listed in container's body (00000000) + True, # and it is bytes size is not counted in types header + "ef0001010004020001000304000400008000013050000bad60a7", + None, + ), + ], +) +def test_code_section_header_body_mismatch( + eof_test: EOFTestFiller, + skip_header_listing: bool, + skip_body_listing: bool, + skip_types_body_listing: bool, + skip_types_header_listing: bool, + expected_code: str, + expected_exception: EOFException, +): + """ + Inconsistent number of code sections (between types and code) + """ + eof_code = Container( + name="EOF1I0018", + sections=[ + Section.Code( + code=Op.ADDRESS + Op.POP + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Code( + code=Op.ADDRESS + Op.POP + Op.STOP, + code_inputs=0, + code_outputs=0, + max_stack_height=0, + # weather to not mention it in code section header list + skip_header_listing=skip_header_listing, + # weather to not print it's code in containers body + skip_body_listing=skip_body_listing, + # weather to not print it's input bytes in containers body + skip_types_body_listing=skip_types_body_listing, + # weather to not calculate it's input bytes size in types section's header + skip_types_header_listing=skip_types_header_listing, + ), + Section.Data("0x0bad60A7"), + ], + ) + + # TODO remove this after Container class implementation is reliable + assert bytes(eof_code).hex() == bytes.fromhex(expected_code).hex() + + eof_test( + data=eof_code, + expect_exception=expected_exception, + ) From 400f7adeaa0dd342b886caa2f966814533126fb7 Mon Sep 17 00:00:00 2001 From: Dimitry Kh Date: Fri, 3 May 2024 12:03:00 +0200 Subject: [PATCH 7/9] fix unit tests and tox --- src/ethereum_test_tools/tests/test_vm.py | 8 ++++---- whitelist.txt | 3 +++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/ethereum_test_tools/tests/test_vm.py b/src/ethereum_test_tools/tests/test_vm.py index 5bfd7ecb9b..6989c13efa 100644 --- a/src/ethereum_test_tools/tests/test_vm.py +++ b/src/ethereum_test_tools/tests/test_vm.py @@ -179,7 +179,7 @@ [ Op.ORIGIN.int(), Op.RJUMPV.int(), - 0x03, # Data portion, defined by the [1, 2, 3] argument + 0x02, # Data portion, defined by the [1, 2, 3] argument 0x00, 0x01, 0x00, @@ -203,7 +203,7 @@ bytes( [ Op.RJUMPV.int(), - 0x03, + 0x02, 0xFF, 0xFF, 0xFF, @@ -218,7 +218,7 @@ bytes( [ Op.RJUMPV.int(), - 0x05, + 0x04, 0x00, 0x00, 0x00, @@ -238,7 +238,7 @@ [ Op.ORIGIN.int(), Op.RJUMPV.int(), - 0x03, # Data portion, defined by the [1, 2, 3] argument + 0x02, # Data portion, defined by the [1, 2, 3] argument 0x00, 0x01, 0x00, diff --git a/whitelist.txt b/whitelist.txt index 4c8ed43eb1..a1442758b2 100644 --- a/whitelist.txt +++ b/whitelist.txt @@ -99,6 +99,7 @@ enum env envvar eof +EOF1 eofparse EOFException esbenp @@ -213,6 +214,7 @@ ommers opc oprypin origin +ori parseable pathlib pdb @@ -313,6 +315,7 @@ u256 ubuntu ukiyo uncomment +undersize util utils v0 From 9a8e27a8b3f7207b4ac1c33bc102361b66bb6fc1 Mon Sep 17 00:00:00 2001 From: Dimitry Kh Date: Fri, 3 May 2024 12:05:11 +0200 Subject: [PATCH 8/9] changelog, address pr review issues --- docs/CHANGELOG.md | 1 + src/ethereum_test_tools/tests/test_vm.py | 3 +++ .../prague/eip3540_eof_v1/test_example_valid_invalid.py | 9 --------- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 9ff425a4f4..cb1dc1a938 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -9,6 +9,7 @@ Test fixtures for use by clients are available for each release on the [Github r ### 🧪 Test Cases - ✨ Add `test_double_kill` and `test_recreate` which test resurrection of accounts killed with `SELFDESTRUCT` ([#488](https://github.com/ethereum/execution-spec-tests/pull/488)). +- ✨ Add eof example valid invalid tests from ori, fetch EOF Container implementation ([#535](https://github.com/ethereum/execution-spec-tests/pull/535)). ### 🛠️ Framework diff --git a/src/ethereum_test_tools/tests/test_vm.py b/src/ethereum_test_tools/tests/test_vm.py index 6989c13efa..a75af52c32 100644 --- a/src/ethereum_test_tools/tests/test_vm.py +++ b/src/ethereum_test_tools/tests/test_vm.py @@ -258,6 +258,9 @@ ] ), ), + (Op.RJUMPV[0, 3, 6, 9], bytes.fromhex("e2030000000300060009")), + (Op.RJUMPV[2, 0], bytes.fromhex("e20100020000")), + (Op.RJUMPV[b"\x02\x00\x02\xFF\xFF"], bytes.fromhex("e2020002ffff")), ], ) def test_opcodes(opcodes: bytes, expected: bytes): diff --git a/tests/prague/eip3540_eof_v1/test_example_valid_invalid.py b/tests/prague/eip3540_eof_v1/test_example_valid_invalid.py index 741b9905a0..a21dc3e719 100644 --- a/tests/prague/eip3540_eof_v1/test_example_valid_invalid.py +++ b/tests/prague/eip3540_eof_v1/test_example_valid_invalid.py @@ -404,15 +404,6 @@ def test_example_valid_invalid( ) -def test_new_eof_opcodes(eof_test: EOFTestFiller): - """ - Assert the compilation of new eof opcodes is valid - """ - assert str(bytes(Op.RJUMPV[0, 3, 6, 9]).hex()) == "e2030000000300060009" - assert str(bytes(Op.RJUMPV[2, 0]).hex()) == "e20100020000" - assert str(bytes(Op.RJUMPV[b"\x02\x00\x02\xFF\xFF"]).hex()) == "e2020002ffff" - - @pytest.mark.parametrize( "skip_header_listing, skip_body_listing, skip_types_body_listing, skip_types_header_listing," "expected_code, expected_exception", From 25a3fc09a8afb8f71f01ecc4b95df431618de6cb Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Wed, 8 May 2024 11:55:29 -0600 Subject: [PATCH 9/9] Apply suggestions from code review --- tests/prague/eip3540_eof_v1/test_example_valid_invalid.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/prague/eip3540_eof_v1/test_example_valid_invalid.py b/tests/prague/eip3540_eof_v1/test_example_valid_invalid.py index a21dc3e719..cdec1fc469 100644 --- a/tests/prague/eip3540_eof_v1/test_example_valid_invalid.py +++ b/tests/prague/eip3540_eof_v1/test_example_valid_invalid.py @@ -390,7 +390,7 @@ def test_example_valid_invalid( eof_test: EOFTestFiller, eof_code: Container, expected_hex_bytecode: str, - exception: EOFException, + exception: EOFException | None, ): """ Verify eof container construction and exception @@ -467,7 +467,7 @@ def test_code_section_header_body_mismatch( skip_types_body_listing: bool, skip_types_header_listing: bool, expected_code: str, - expected_exception: EOFException, + expected_exception: EOFException | None, ): """ Inconsistent number of code sections (between types and code)