diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 4192c29a1a..4b1d987a6c 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -16,8 +16,8 @@ Test fixtures for use by clients are available for each release on the [Github r - ✨ Add tests for [EIP-6110: Supply validator deposits on chain](https://eips.ethereum.org/EIPS/eip-6110) ([#530](https://github.com/ethereum/execution-spec-tests/pull/530)). - ✨ Add tests for [EIP-7002: Execution layer triggerable withdrawals](https://eips.ethereum.org/EIPS/eip-7002) ([#530](https://github.com/ethereum/execution-spec-tests/pull/530)). - ✨ Add tests for [EIP-7685: General purpose execution layer requests](https://eips.ethereum.org/EIPS/eip-7685) ([#530](https://github.com/ethereum/execution-spec-tests/pull/530)). -- ✨ Add tests for [EIP-2935: Serve historical block hashes from state -](https://eips.ethereum.org/EIPS/eip-2935) ([#564](https://github.com/ethereum/execution-spec-tests/pull/564)). +- ✨ Add tests for [EIP-2935: Serve historical block hashes from state](https://eips.ethereum.org/EIPS/eip-2935) ([#564](https://github.com/ethereum/execution-spec-tests/pull/564)). +- ✨ Add tests for [EIP-4200: EOF - Static relative jumps](https://eips.ethereum.org/EIPS/eip-4200) ([#581](https://github.com/ethereum/execution-spec-tests/pull/581)). ### 🛠️ Framework diff --git a/src/ethereum_test_tools/exceptions/evmone_exceptions.py b/src/ethereum_test_tools/exceptions/evmone_exceptions.py index a51d65c995..efe6b3dd71 100644 --- a/src/ethereum_test_tools/exceptions/evmone_exceptions.py +++ b/src/ethereum_test_tools/exceptions/evmone_exceptions.py @@ -69,6 +69,7 @@ class EvmoneExceptionMapper: ), ExceptionMessage(EOFException.INVALID_MAX_STACK_HEIGHT, "err: invalid_max_stack_height"), ExceptionMessage(EOFException.INVALID_DATALOADN_INDEX, "err: invalid_dataloadn_index"), + ExceptionMessage(EOFException.TRUNCATED_INSTRUCTION, "err: truncated_instruction"), ) def __init__(self) -> None: diff --git a/src/ethereum_test_tools/exceptions/exceptions.py b/src/ethereum_test_tools/exceptions/exceptions.py index b9261a8f49..261a1bad5b 100644 --- a/src/ethereum_test_tools/exceptions/exceptions.py +++ b/src/ethereum_test_tools/exceptions/exceptions.py @@ -680,6 +680,10 @@ class EOFException(ExceptionBase): """ A DATALOADN instruction has out-of-bounds index for the data section. """ + TRUNCATED_INSTRUCTION = auto() + """ + EOF container's code section has truncated instruction. + """ """ diff --git a/tests/prague/eip7692_eof_v1/eip4200_relative_jumps/__init__.py b/tests/prague/eip7692_eof_v1/eip4200_relative_jumps/__init__.py new file mode 100644 index 0000000000..2f3b343750 --- /dev/null +++ b/tests/prague/eip7692_eof_v1/eip4200_relative_jumps/__init__.py @@ -0,0 +1,3 @@ +""" +EOF tests for EIP-4200 relative jumps +""" diff --git a/tests/prague/eip7692_eof_v1/eip4200_relative_jumps/helpers.py b/tests/prague/eip7692_eof_v1/eip4200_relative_jumps/helpers.py new file mode 100644 index 0000000000..a1f3a161cb --- /dev/null +++ b/tests/prague/eip7692_eof_v1/eip4200_relative_jumps/helpers.py @@ -0,0 +1,26 @@ +""" +EOF RJump tests helpers +""" +import itertools +from enum import Enum + +"""Storage addresses for common testing fields""" +_slot = itertools.count() +next(_slot) # don't use slot 0 +slot_code_worked = next(_slot) +slot_conditional_result = next(_slot) +slot_last_slot = next(_slot) + +"""Storage values for common testing fields""" +value_code_worked = 0x2015 +value_calldata_true = 10 +value_calldata_false = 11 + + +class JumpDirection(Enum): + """ + Enum for the direction of the jump + """ + + FORWARD = 1 + BACKWARD = -1 diff --git a/tests/prague/eip7692_eof_v1/eip4200_relative_jumps/test_rjump.py b/tests/prague/eip7692_eof_v1/eip4200_relative_jumps/test_rjump.py new file mode 100644 index 0000000000..0a385f23da --- /dev/null +++ b/tests/prague/eip7692_eof_v1/eip4200_relative_jumps/test_rjump.py @@ -0,0 +1,626 @@ +""" +EOF JUMPF tests covering stack and code validation rules. +""" + +import pytest + +from ethereum_test_tools import Account, EOFException, EOFStateTestFiller, EOFTestFiller +from ethereum_test_tools.eof.v1 import Container, Section +from ethereum_test_tools.eof.v1.constants import NON_RETURNING_SECTION +from ethereum_test_tools.vm.opcode import Opcodes as Op + +from .. import EOF_FORK_NAME +from .helpers import JumpDirection, slot_code_worked, value_code_worked + +REFERENCE_SPEC_GIT_PATH = "EIPS/eip-4200.md" +REFERENCE_SPEC_VERSION = "17d4a8d12d2b5e0f2985c866376c16c8c6df7cba" + +pytestmark = pytest.mark.valid_from(EOF_FORK_NAME) + + +def test_rjump_positive_negative( + eof_state_test: EOFStateTestFiller, +): + """EOF1V4200_0001 (Valid) EOF code containing RJUMP (Positive, Negative)""" + eof_state_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH0 + + Op.RJUMPI[3] + + Op.RJUMP[7] + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP + + Op.RJUMP[-10], + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ) + ], + ), + container_post=Account(storage={slot_code_worked: value_code_worked}), + ) + + +def test_rjump_positive_negative_with_data( + eof_state_test: EOFStateTestFiller, +): + """EOF1V4200_0001 (Valid) EOF code containing RJUMP (Positive, Negative)""" + eof_state_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH0 + + Op.RJUMPI[3] + + Op.RJUMP[7] + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP + + Op.RJUMP[-10], + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ), + Section.Data(data=b"\xde\xad\xbe\xef"), + ], + ), + container_post=Account(storage={slot_code_worked: value_code_worked}), + ) + + +def test_rjump_zero( + eof_state_test: EOFStateTestFiller, +): + """EOF1V4200_0002 (Valid) EOF code containing RJUMP (Zero)""" + eof_state_test( + data=Container( + sections=[ + Section.Code( + code=Op.RJUMP[0] + Op.SSTORE(slot_code_worked, value_code_worked) + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ) + ], + ), + container_post=Account(storage={slot_code_worked: value_code_worked}), + ) + + +def test_rjump_maxes( + eof_state_test: EOFStateTestFiller, +): + """EOF1V4200_0003 EOF with RJUMP containing the maximum positive and negative offset (32767)""" + eof_state_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH0 + + Op.RJUMPI[3] # The push/jumpi is to allow the NOOPs to be forward referenced + + Op.RJUMP[0x7FFF] + + Op.NOOP * (0x7FFF - 7) + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP + + Op.RJUMP[0x8000], + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ) + ], + ), + container_post=Account(storage={slot_code_worked: value_code_worked}), + ) + + +def test_rjump_truncated_rjump( + eof_test: EOFTestFiller, +): + """EOF1I4200_0001 (Invalid) EOF code containing truncated RJUMP""" + eof_test( + data=Container( + sections=[Section.Code(code=Op.RJUMP, code_outputs=NON_RETURNING_SECTION)], + ), + expect_exception=EOFException.TRUNCATED_INSTRUCTION, + ) + + +def test_rjump_truncated_rjump_2( + eof_test: EOFTestFiller, +): + """EOF1I4200_0002 (Invalid) EOF code containing truncated RJUMP""" + eof_test( + data=Container( + sections=[Section.Code(code=Op.RJUMP + Op.STOP, code_outputs=NON_RETURNING_SECTION)], + ), + expect_exception=EOFException.TRUNCATED_INSTRUCTION, + ) + + +def test_rjump_into_header( + eof_test: EOFTestFiller, +): + """ + EOF1I4200_0003 (Invalid) EOF code containing RJUMP with target outside code bounds + (Jumping into header) + """ + eof_test( + data=Container( + sections=[ + Section.Code(code=Op.RJUMP[-5], code_outputs=NON_RETURNING_SECTION), + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +def test_rjump_before_header( + eof_test: EOFTestFiller, +): + """ + EOF1I4200_0004 (Invalid) EOF code containing RJUMP with target outside code bounds + (Jumping before code begin) + """ + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.RJUMP[-23], + code_outputs=NON_RETURNING_SECTION, + ), + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +def test_rjump_into_data( + eof_test: EOFTestFiller, +): + """ + EOF1I4200_0005 (Invalid) EOF code containing RJUMP with target outside code bounds + (Jumping into data section) + """ + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.RJUMP[2], + code_outputs=NON_RETURNING_SECTION, + ), + Section.Data(data=b"\xaa\xbb\xcc"), + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +def test_rjump_outside_other_section_before( + eof_test: EOFTestFiller, +): + """EOF code containing RJUMP with target outside code bounds (prior code section)""" + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.JUMPF[1], + code_outputs=NON_RETURNING_SECTION, + ), + Section.Code( + code=Op.RJUMP[-6], + code_outputs=NON_RETURNING_SECTION, + ), + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +def test_rjump_outside_other_section_after( + eof_test: EOFTestFiller, +): + """EOF code containing RJUMP with target outside code bounds (Subsequent code section)""" + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.JUMPF[1], + code_outputs=NON_RETURNING_SECTION, + ), + Section.Code( + code=Op.RJUMP[3] + Op.JUMPF[2], + code_outputs=NON_RETURNING_SECTION, + ), + Section.Code( + code=Op.STOP, + code_outputs=NON_RETURNING_SECTION, + ), + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +def test_rjump_after_container( + eof_test: EOFTestFiller, +): + """ + EOF1I4200_0006 (Invalid) EOF code containing RJUMP with target outside code bounds + (Jumping after code end) + """ + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.RJUMP[2], + code_outputs=NON_RETURNING_SECTION, + ), + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +def test_rjump_to_code_end( + eof_test: EOFTestFiller, +): + """ + EOF1I4200_0007 (Invalid) EOF code containing RJUMP with target outside code bounds + (Jumping to code end) + """ + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.RJUMP[1] + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + ), + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +def test_rjump_into_self( + eof_test: EOFTestFiller, +): + """EOF1I4200_0008 (Invalid) EOF code containing RJUMP with target self RJUMP immediate""" + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.RJUMP[-1], + code_outputs=NON_RETURNING_SECTION, + ), + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +def test_rjump_into_rjump( + eof_test: EOFTestFiller, +): + """EOF1I4200_0009 (Invalid) EOF code containing RJUMP with target other RJUMP immediate""" + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.RJUMP[1] + Op.RJUMP[0], + code_outputs=NON_RETURNING_SECTION, + ), + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +def test_rjump_into_rjumpi( + eof_test: EOFTestFiller, +): + """EOF1I4200_0010 (Invalid) EOF code containing RJUMP with target RJUMPI immediate""" + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.RJUMP[5] + Op.STOP + Op.PUSH1(1) + Op.RJUMPI[-6] + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ) + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +@pytest.mark.parametrize("jump", [JumpDirection.FORWARD, JumpDirection.BACKWARD]) +def test_rjump_into_push_1(eof_test: EOFTestFiller, jump: JumpDirection): + """EOF1I4200_0011 (Invalid) EOF code containing RJUMP with target PUSH1 immediate""" + code = ( + Op.PUSH1[1] + Op.RJUMP[-4] if jump == JumpDirection.BACKWARD else Op.RJUMP[1] + Op.PUSH1[1] + ) + eof_test( + data=Container( + sections=[ + Section.Code( + code=code, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +@pytest.mark.parametrize( + "opcode", + [ + Op.PUSH2, + Op.PUSH3, + Op.PUSH4, + Op.PUSH5, + Op.PUSH6, + Op.PUSH7, + Op.PUSH8, + Op.PUSH9, + Op.PUSH10, + Op.PUSH11, + Op.PUSH12, + Op.PUSH13, + Op.PUSH14, + Op.PUSH15, + Op.PUSH16, + Op.PUSH17, + Op.PUSH18, + Op.PUSH19, + Op.PUSH20, + Op.PUSH21, + Op.PUSH22, + Op.PUSH23, + Op.PUSH24, + Op.PUSH25, + Op.PUSH26, + Op.PUSH27, + Op.PUSH28, + Op.PUSH29, + Op.PUSH30, + Op.PUSH31, + Op.PUSH32, + ], +) +@pytest.mark.parametrize("jump", [JumpDirection.FORWARD, JumpDirection.BACKWARD]) +@pytest.mark.parametrize( + "data_portion_end", + [True, False], + ids=["data_portion_end", "data_portion_start"], +) +def test_rjump_into_push_n( + eof_test: EOFTestFiller, + opcode: Op, + jump: JumpDirection, + data_portion_end: bool, +): + """EOF1I4200_0011 (Invalid) EOF code containing RJUMP with target PUSH2+ immediate""" + data_portion_length = int.from_bytes(opcode, byteorder="big") - 0x5F + if jump == JumpDirection.FORWARD: + offset = data_portion_length if data_portion_end else 1 + code = Op.RJUMP[offset] + opcode[0] + else: + offset = -4 if data_portion_end else -4 - data_portion_length + 1 + code = opcode[0] + Op.RJUMP[offset] + eof_test( + data=Container( + sections=[ + Section.Code( + code=code, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +@pytest.mark.parametrize("target_rjumpv_table_size", [1, 256]) +@pytest.mark.parametrize( + "data_portion_end", + [True, False], + ids=["data_portion_end", "data_portion_start"], +) +def test_rjump_into_rjumpv( + eof_test: EOFTestFiller, + target_rjumpv_table_size: int, + data_portion_end: bool, +): + """EOF1I4200_0012 (Invalid) EOF code containing RJUMP with target RJUMPV immediate""" + invalid_destination = 4 + (2 * target_rjumpv_table_size) if data_portion_end else 4 + target_jump_table = [0 for _ in range(target_rjumpv_table_size)] + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.RJUMP[invalid_destination] + + Op.STOP + + Op.PUSH1(1) + + Op.RJUMPV[target_jump_table] + + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ) + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +@pytest.mark.parametrize( + "data_portion_end", + [True, False], + ids=["data_portion_end", "data_portion_start"], +) +def test_rjump_into_callf( + eof_test: EOFTestFiller, + data_portion_end: bool, +): + """EOF1I4200_0013 (Invalid) EOF code containing RJUMP with target CALLF immediate""" + invalid_destination = 2 if data_portion_end else 1 + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.RJUMP[invalid_destination] + Op.CALLF[1] + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + ), + Section.Code( + code=Op.SSTORE(1, 1) + Op.RETF, + code_outputs=0, + max_stack_height=2, + ), + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +def test_rjump_into_dupn( + eof_test: EOFTestFiller, +): + """EOF code containing RJUMP with target DUPN immediate""" + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(1) + + Op.PUSH1(1) + + Op.RJUMP[1] + + Op.DUPN[1] + + Op.SSTORE + + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ), + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +def test_rjump_into_swapn( + eof_test: EOFTestFiller, +): + """EOF code containing RJUMP with target SWAPN immediate""" + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(1) + + Op.PUSH1(1) + + Op.RJUMP[1] + + Op.SWAPN[1] + + Op.SSTORE + + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ), + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +def test_rjump_into_exchange( + eof_test: EOFTestFiller, +): + """EOF code containing RJUMP with target EXCHANGE immediate""" + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(1) + + Op.PUSH1(2) + + Op.PUSH1(3) + + Op.RJUMP[1] + + Op.EXCHANGE[0x00] + + Op.SSTORE + + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=3, + ), + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +def test_rjump_into_eofcreate( + eof_test: EOFTestFiller, +): + """EOF code containing RJUMP with target EOFCREATE immediate""" + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.RJUMP[1] + Op.EOFCREATE[0](0, 0, 0, 0) + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=4, + ), + Section.Container( + container=Container( + sections=[ + Section.Code( + code=Op.RETURNCONTRACT[0](0, 0), + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ), + Section.Container( + container=Container( + sections=[ + Section.Code( + code=Op.STOP, + code_outputs=NON_RETURNING_SECTION, + ) + ] + ) + ), + ] + ) + ), + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +def test_rjump_into_returncontract( + eof_test: EOFTestFiller, +): + """EOF code containing RJUMP with target RETURNCONTRACT immediate""" + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.EOFCREATE[0](0, 0, 0, 0) + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=4, + ), + Section.Container( + container=Container( + sections=[ + Section.Code( + code=Op.RJUMP[5] + Op.RETURNCONTRACT[0](0, 0), + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ), + Section.Container( + container=Container( + sections=[ + Section.Code( + code=Op.STOP, + code_outputs=NON_RETURNING_SECTION, + ) + ] + ) + ), + ] + ) + ), + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) diff --git a/tests/prague/eip7692_eof_v1/eip4200_relative_jumps/test_rjumpi.py b/tests/prague/eip7692_eof_v1/eip4200_relative_jumps/test_rjumpi.py new file mode 100644 index 0000000000..fa9aa418ac --- /dev/null +++ b/tests/prague/eip7692_eof_v1/eip4200_relative_jumps/test_rjumpi.py @@ -0,0 +1,796 @@ +""" +EOF JUMPF tests covering stack and code validation rules. +""" + +import pytest + +from ethereum_test_tools import ( + Account, + Environment, + EOFException, + EOFStateTestFiller, + EOFTestFiller, + StateTestFiller, + TestAddress, + Transaction, +) +from ethereum_test_tools.eof.v1 import Container, Section +from ethereum_test_tools.eof.v1.constants import NON_RETURNING_SECTION +from ethereum_test_tools.vm.opcode import Opcodes as Op + +from .. import EOF_FORK_NAME +from .helpers import ( + JumpDirection, + slot_code_worked, + slot_conditional_result, + value_calldata_false, + value_calldata_true, + value_code_worked, +) + +REFERENCE_SPEC_GIT_PATH = "EIPS/eip-4200.md" +REFERENCE_SPEC_VERSION = "17d4a8d12d2b5e0f2985c866376c16c8c6df7cba" + +pytestmark = pytest.mark.valid_from(EOF_FORK_NAME) + + +@pytest.mark.parametrize( + "calldata", + [pytest.param(b"\x00", id="False"), pytest.param(b"\x01", id="True")], +) +def test_rjumpi_condition_forwards( + state_test: StateTestFiller, + calldata: bytes, +): + """Test RJUMPI contract switching based on external input""" + env = Environment() + tx = Transaction( + nonce=1, + gas_limit=10_000_000, + data=calldata, + ) + pre = { + TestAddress: Account(balance=10**18, nonce=tx.nonce), + tx.to: Account( + code=Container( + sections=[ + Section.Code( + code=Op.PUSH1(0) + + Op.CALLDATALOAD + + Op.RJUMPI[6] + + Op.SSTORE(slot_conditional_result, value_calldata_false) + + Op.STOP + + Op.SSTORE(slot_conditional_result, value_calldata_true) + + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ) + ] + ), + nonce=1, + ), + } + post = { + tx.to: Account( + storage={ + slot_conditional_result: value_calldata_false + if calldata == b"\0" + else value_calldata_true + } + ) + } + state_test(env=env, tx=tx, pre=pre, post=post) + + +@pytest.mark.parametrize( + "calldata", + [pytest.param(b"\x00", id="False"), pytest.param(b"\x01", id="True")], +) +def test_rjumpi_condition_backwards( + state_test: StateTestFiller, + calldata: bytes, +): + """Test RJUMPI contract switching based on external input""" + env = Environment() + tx = Transaction( + nonce=1, + gas_limit=10_000_000, + data=calldata, + ) + pre = { + TestAddress: Account(balance=10**18, nonce=tx.nonce), + tx.to: Account( + code=Container( + sections=[ + Section.Code( + code=Op.PUSH1(1) + + Op.RJUMPI[6] + + Op.SSTORE(slot_conditional_result, value_calldata_true) + + Op.STOP + + Op.PUSH0 + + Op.CALLDATALOAD + + Op.RJUMPI[-11] + + Op.SSTORE(slot_conditional_result, value_calldata_false) + + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ) + ] + ), + nonce=1, + ), + } + post = { + tx.to: Account( + storage={ + slot_conditional_result: value_calldata_false + if calldata == b"\0" + else value_calldata_true + } + ) + } + state_test(env=env, tx=tx, pre=pre, post=post) + + +@pytest.mark.parametrize( + "calldata", + [pytest.param(b"\x00", id="False"), pytest.param(b"\x01", id="True")], +) +def test_rjumpi_condition_zero( + state_test: StateTestFiller, + calldata: bytes, +): + """Test RJUMPI contract switching based on external input""" + env = Environment() + tx = Transaction( + nonce=1, + gas_limit=10_000_000, + data=calldata, + ) + pre = { + TestAddress: Account(balance=10**18, nonce=tx.nonce), + tx.to: Account( + code=Container( + sections=[ + Section.Code( + code=Op.PUSH0 + + Op.CALLDATALOAD + + Op.RJUMPI[0] + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ) + ] + ), + nonce=1, + ), + } + post = {tx.to: Account(storage={slot_code_worked: value_code_worked})} + state_test(env=env, tx=tx, pre=pre, post=post) + + +def test_rjumpi_forwards( + eof_state_test: EOFStateTestFiller, +): + """EOF1V4200_0004 (Valid) EOF code containing RJUMPI (Positive)""" + eof_state_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(1) + + Op.RJUMPI[3] + + Op.NOOP + + Op.NOOP + + Op.STOP + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ) + ], + ), + container_post=Account(storage={slot_code_worked: value_code_worked}), + ) + + +def test_rjumpi_backwards( + eof_state_test: EOFStateTestFiller, +): + """EOF1V4200_0005 (Valid) EOF code containing RJUMPI (Negative)""" + eof_state_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(1) + + Op.RJUMPI[7] + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP + + Op.PUSH1(1) + + Op.RJUMPI[-12] + + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ) + ], + ), + container_post=Account(storage={slot_code_worked: value_code_worked}), + ) + + +def test_rjumpi_zero( + eof_state_test: EOFStateTestFiller, +): + """EOF1V4200_0006 (Valid) EOF code containing RJUMPI (Zero)""" + eof_state_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(1) + + Op.RJUMPI[0] + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ) + ], + ), + container_post=Account(storage={slot_code_worked: value_code_worked}), + ) + + +def test_rjumpi_max_forward( + eof_state_test: EOFStateTestFiller, +): + """EOF1V4200_0007 (Valid) EOF with RJUMPI containing the maximum offset (32767)""" + eof_state_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(1) + + Op.RJUMPI[32767] + + Op.NOOP * 32768 + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ) + ], + ), + container_post=Account(storage={slot_code_worked: value_code_worked}), + ) + + +def test_rjumpi_max_backward( + eof_state_test: EOFStateTestFiller, +): + """EOF with RJUMPI containing the maximum negative offset (-32768)""" + eof_state_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH0 + + Op.RJUMPI[0x7FFF] + + Op.NOOP * (0x7FFF - 7) + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP + + Op.PUSH0 + + Op.RJUMPI[0x8000] + + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ) + ], + ), + container_post=Account(storage={slot_code_worked: value_code_worked}), + ), + + +def test_rjump_truncated( + eof_test: EOFTestFiller, +): + """EOF1I4200_0014 (Invalid) EOF code containing truncated RJUMPI""" + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(0) + Op.RJUMPI, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ) + ], + ), + expect_exception=EOFException.TRUNCATED_INSTRUCTION, + ) + + +def test_rjump_truncated_2( + eof_test: EOFTestFiller, +): + """EOF1I4200_0015 (Invalid) EOF code containing truncated RJUMPI""" + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(0) + Op.RJUMPI + b"\0", + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ) + ], + ), + expect_exception=EOFException.TRUNCATED_INSTRUCTION, + ) + + +def test_rjumpi_into_header( + eof_test: EOFTestFiller, +): + """ + EOF1I4200_0016 (Invalid) EOF code containing RJUMPI with target outside code bounds + (Jumping into header) + """ + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(1) + Op.RJUMPI[-7] + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ) + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +def test_rjumpi_jump_before_header( + eof_test: EOFTestFiller, +): + """ + EOF1I4200_0017 (Invalid) EOF code containing RJUMPI with target outside code bounds + (Jumping to before code begin) + """ + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(1) + Op.RJUMPI[-25] + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ) + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +def test_rjumpi_into_data( + eof_test: EOFTestFiller, +): + """ + EOF1I4200_0018 (Invalid) EOF code containing RJUMPI with target outside code bounds + (Jumping into data section) + """ + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(1) + Op.RJUMPI[2] + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data(data=b"\xaa\xbb\xcc"), + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +def test_rjumpi_after_container( + eof_test: EOFTestFiller, +): + """ + EOF1I4200_0019 (Invalid) EOF code containing RJUMPI with target outside code bounds + (Jumping to after code end) + """ + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(1) + Op.RJUMPI[2] + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ) + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +def test_rjumpi_to_code_end( + eof_test: EOFTestFiller, +): + """ + EOF1I4200_0020 (Invalid) EOF code containing RJUMPI with target outside code bounds + (Jumping to code end) + """ + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(1) + Op.RJUMPI[1] + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +def test_rjumpi_into_self( + eof_test: EOFTestFiller, +): + """EOF1I4200_0021 (Invalid) EOF code containing RJUMPI with target same RJUMPI immediate""" + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(1) + Op.RJUMPI[-1] + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ) + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +def test_rjumpi_into_rjump( + eof_test: EOFTestFiller, +): + """EOF1I4200_0023 (Invalid) EOF code containing RJUMPI with target RJUMP immediate""" + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(1) + Op.RJUMPI[3] + Op.STOP + Op.RJUMP[-9], + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ) + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +def test_rjumpi_into_rjumpi( + eof_test: EOFTestFiller, +): + """EOF1I4200_0022 (Invalid) EOF code containing RJUMPI with target other RJUMPI immediate""" + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(1) + + Op.RJUMPI[5] + + Op.STOP + + Op.PUSH1(1) + + Op.RJUMPI[-11] + + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ) + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +@pytest.mark.parametrize("jump", [JumpDirection.FORWARD, JumpDirection.BACKWARD]) +def test_rjumpi_into_push_1( + eof_test: EOFTestFiller, + jump: JumpDirection, +): + """EOF1I4200_0024 (Invalid) EOF code containing RJUMPI with target PUSH1 immediate""" + code = ( + Op.PUSH1(1) + Op.RJUMPI[-4] + Op.STOP + if jump == JumpDirection.BACKWARD + else Op.PUSH1(1) + Op.RJUMPI[1] + Op.STOP + ) + eof_test( + data=Container( + sections=[ + Section.Code( + code=code, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ) + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +@pytest.mark.parametrize( + "opcode", + [ + Op.PUSH2, + Op.PUSH3, + Op.PUSH4, + Op.PUSH5, + Op.PUSH6, + Op.PUSH7, + Op.PUSH8, + Op.PUSH9, + Op.PUSH10, + Op.PUSH11, + Op.PUSH12, + Op.PUSH13, + Op.PUSH14, + Op.PUSH15, + Op.PUSH16, + Op.PUSH17, + Op.PUSH18, + Op.PUSH19, + Op.PUSH20, + Op.PUSH21, + Op.PUSH22, + Op.PUSH23, + Op.PUSH24, + Op.PUSH25, + Op.PUSH26, + Op.PUSH27, + Op.PUSH28, + Op.PUSH29, + Op.PUSH30, + Op.PUSH31, + Op.PUSH32, + ], +) +@pytest.mark.parametrize("jump", [JumpDirection.FORWARD, JumpDirection.BACKWARD]) +@pytest.mark.parametrize( + "data_portion_end", + [True, False], + ids=["data_portion_end", "data_portion_start"], +) +def test_rjumpi_into_push_n( + eof_test: EOFTestFiller, + opcode: Op, + jump: JumpDirection, + data_portion_end: bool, +): + """EOF1I4200_0024 (Invalid) EOF code containing RJUMPI with target PUSH2+ immediate""" + data_portion_length = int.from_bytes(opcode, byteorder="big") - 0x5F + if jump == JumpDirection.FORWARD: + offset = data_portion_length if data_portion_end else 1 + code = Op.PUSH1(1) + Op.RJUMPI[offset] + opcode[0] + else: + offset = -4 if data_portion_end else -4 - data_portion_length + 1 + code = opcode[0] + Op.RJUMPI[offset] + eof_test( + data=Container( + sections=[ + Section.Code( + code=code, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ) + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +@pytest.mark.parametrize("target_rjumpv_table_size", [1, 256]) +@pytest.mark.parametrize( + "data_portion_end", + [True, False], + ids=["data_portion_end", "data_portion_start"], +) +def test_rjumpi_into_rjumpv( + eof_test: EOFTestFiller, + target_rjumpv_table_size: int, + data_portion_end: bool, +): + """EOF1I4200_0025 (Invalid) EOF code containing RJUMPI with target RJUMPV immediate""" + invalid_destination = 4 + (2 * target_rjumpv_table_size) if data_portion_end else 4 + target_jump_table = [0 for _ in range(target_rjumpv_table_size)] + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(1) + + Op.RJUMPI[invalid_destination] + + Op.STOP + + Op.PUSH1(1) + + Op.RJUMPV[target_jump_table] + + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ) + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +@pytest.mark.parametrize( + "data_portion_end", + [True, False], + ids=["data_portion_end", "data_portion_start"], +) +def test_rjumpi_into_callf( + eof_test: EOFTestFiller, + data_portion_end: bool, +): + """EOF1I4200_0026 (Invalid) EOF code containing RJUMPI with target CALLF immediate""" + invalid_destination = 2 if data_portion_end else 1 + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(1) + Op.RJUMPI[invalid_destination] + Op.CALLF[1] + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Code( + code=Op.SSTORE(1, 1) + Op.RETF, + code_outputs=0, + max_stack_height=2, + ), + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +def test_rjumpi_into_dupn( + eof_test: EOFTestFiller, +): + """EOF code containing RJUMP with target DUPN immediate""" + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(1) + + Op.PUSH1(1) + + Op.PUSH1(1) + + Op.RJUMPI[1] + + Op.DUPN[1] + + Op.SSTORE + + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=3, + ), + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +def test_rjumpi_into_swapn( + eof_test: EOFTestFiller, +): + """EOF code containing RJUMP with target SWAPN immediate""" + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(1) + + Op.PUSH1(1) + + Op.PUSH1(1) + + Op.RJUMPI[1] + + Op.SWAPN[1] + + Op.SSTORE + + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=3, + ), + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +def test_rjump_into_exchange( + eof_test: EOFTestFiller, +): + """EOF code containing RJUMP with target EXCHANGE immediate""" + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(1) + + Op.PUSH1(2) + + Op.PUSH1(3) + + Op.PUSH1(1) + + Op.RJUMPI[1] + + Op.EXCHANGE[0x00] + + Op.SSTORE + + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=4, + ), + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +def test_rjumpi_into_eofcreate( + eof_test: EOFTestFiller, +): + """EOF code containing RJUMP with target EOFCREATE immediate""" + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH0 + Op.RJUMPI[9] + Op.EOFCREATE[0](0, 0, 0, 0) + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=4, + ), + Section.Container( + container=Container( + sections=[ + Section.Code( + code=Op.RETURNCONTRACT[0](0, 0), + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ), + Section.Container( + container=Container( + sections=[ + Section.Code( + code=Op.STOP, + code_outputs=NON_RETURNING_SECTION, + ) + ] + ) + ), + ] + ) + ), + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +def test_rjumpi_into_returncontract( + eof_test: EOFTestFiller, +): + """EOF code containing RJUMP with target RETURNCONTRACT immediate""" + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.EOFCREATE[0](0, 0, 0, 0) + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=4, + ), + Section.Container( + container=Container( + sections=[ + Section.Code( + code=Op.PUSH0 + Op.RJUMPI[5] + Op.RETURNCONTRACT[0](0, 0), + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ), + Section.Container( + container=Container( + sections=[ + Section.Code( + code=Op.STOP, + code_outputs=NON_RETURNING_SECTION, + ) + ] + ) + ), + ] + ) + ), + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) diff --git a/tests/prague/eip7692_eof_v1/eip4200_relative_jumps/test_rjumpv.py b/tests/prague/eip7692_eof_v1/eip4200_relative_jumps/test_rjumpv.py new file mode 100644 index 0000000000..15abf46df3 --- /dev/null +++ b/tests/prague/eip7692_eof_v1/eip4200_relative_jumps/test_rjumpv.py @@ -0,0 +1,1122 @@ +""" +EOF JUMPF tests covering stack and code validation rules. +""" + +import pytest + +from ethereum_test_tools import Account, EOFException, EOFStateTestFiller, EOFTestFiller +from ethereum_test_tools.eof.v1 import Container, Section +from ethereum_test_tools.eof.v1.constants import NON_RETURNING_SECTION +from ethereum_test_tools.vm.opcode import Opcodes as Op + +from .. import EOF_FORK_NAME +from .helpers import JumpDirection, slot_code_worked, slot_conditional_result, value_code_worked + +REFERENCE_SPEC_GIT_PATH = "EIPS/eip-4200.md" +REFERENCE_SPEC_VERSION = "17d4a8d12d2b5e0f2985c866376c16c8c6df7cba" + +pytestmark = pytest.mark.valid_from(EOF_FORK_NAME) + + +@pytest.mark.parametrize( + "calldata", + [ + pytest.param(0, id="c0"), + pytest.param(1, id="c1"), + pytest.param(3, id="c3"), + pytest.param(255, id="c255"), + pytest.param(256, id="c256"), + pytest.param(2**256 - 1, id="c2^256-1"), + ], +) +@pytest.mark.parametrize( + "table_size", + [ + pytest.param(1, id="t1"), + pytest.param(3, id="t3"), + pytest.param(256, id="t256"), + ], +) +def test_rjumpv_condition( + eof_state_test: EOFStateTestFiller, + calldata: int, + table_size: int, +): + """Test RJUMPV contract switching based on external input""" + value_fall_through = 0xFFFF + value_base = 0x1000 # Force a `PUSH2` instruction to be used on all targets + target_length = 7 + jump_table = [(i + 1) * target_length for i in range(table_size)] + + jump_targets = b"" + for i in range(table_size): + jump_targets += Op.SSTORE(slot_conditional_result, i + value_base) + Op.STOP + + fall_through_case = Op.SSTORE(slot_conditional_result, value_fall_through) + Op.STOP + + eof_state_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH0 + + Op.CALLDATALOAD + + Op.RJUMPV[jump_table] + + fall_through_case + + jump_targets, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ) + ] + ), + tx_data=calldata.to_bytes(32, "big"), + container_post=Account( + storage={ + slot_conditional_result: calldata + value_base + if calldata < table_size + else value_fall_through, + } + ), + ) + + +def test_rjumpv_forwards( + eof_state_test: EOFStateTestFiller, +): + """EOF1V4200_0008 (Valid) EOF with RJUMPV table size 1 (Positive)""" + eof_state_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(0) + + Op.RJUMPV[3] + + Op.NOOP + + Op.NOOP + + Op.STOP + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ) + ], + ), + container_post=Account(storage={slot_code_worked: value_code_worked}), + ) + + +def test_rjumpv_backwards( + eof_state_test: EOFStateTestFiller, +): + """EOF1V4200_0009 (Valid) EOF with RJUMPV table size 1 (Negative)""" + eof_state_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(0) + + Op.RJUMPI[7] + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP + + Op.PUSH1(0) + + Op.RJUMPV[-13] + + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ) + ], + ), + container_post=Account(storage={slot_code_worked: value_code_worked}), + ) + + +def test_rjumpv_zero( + eof_state_test: EOFStateTestFiller, +): + """EOF1V4200_0010 (Valid) EOF with RJUMPV table size 1 (Zero)""" + eof_state_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(0) + + Op.RJUMPV[0] + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ) + ], + ), + container_post=Account(storage={slot_code_worked: value_code_worked}), + ) + + +def test_rjumpv_size_3( + eof_state_test: EOFStateTestFiller, +): + """EOF1V4200_0011 (Valid) EOF with RJUMPV table size 3""" + eof_state_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(0) + + Op.RJUMPV[3, 0, -10] + + Op.NOOP + + Op.NOOP + + Op.STOP + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ) + ], + ), + container_post=Account(storage={slot_code_worked: value_code_worked}), + ) + + +def test_rjumpv_full_table( + eof_state_test: EOFStateTestFiller, +): + """EOF1V4200_0012 (Valid) EOF with RJUMPV table size 256 (Target 0)""" + eof_state_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(0) + + Op.RJUMPV[range(256)] + + Op.NOOP * 256 + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ) + ], + ), + container_post=Account(storage={slot_code_worked: value_code_worked}), + ) + + +def test_rjumpv_full_table_mid( + eof_state_test: EOFStateTestFiller, +): + """EOF1V4200_0013 (Valid) EOF with RJUMPV table size 256 (Target 100)""" + eof_state_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(100) + + Op.RJUMPV[range(256)] + + Op.NOOP * 256 + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ) + ], + ), + container_post=Account(storage={slot_code_worked: value_code_worked}), + ) + + +def test_rjumpv_full_table_end( + eof_state_test: EOFStateTestFiller, +): + """EOF1V4200_0014 (Valid) EOF with RJUMPV table size 256 (Target 254)""" + eof_state_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(254) + + Op.RJUMPV[range(256)] + + Op.NOOP * 256 + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ) + ], + ), + container_post=Account(storage={slot_code_worked: value_code_worked}), + ) + + +def test_rjumpv_full_table_last( + eof_state_test: EOFStateTestFiller, +): + """EOF1V4200_0015 (Valid) EOF with RJUMPV table size 256 (Target 256)""" + eof_state_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH2(256) + + Op.RJUMPV[range(256)] + + Op.NOOP * 256 + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ) + ], + ), + container_post=Account(storage={slot_code_worked: value_code_worked}), + ) + + +def test_rjumpv_max_forwards( + eof_state_test: EOFStateTestFiller, +): + """EOF1V4200_0016 (Valid) EOF with RJUMPV containing the maximum offset (32767)""" + eof_state_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(1) + + Op.RJUMPV[32767] + + Op.NOOP * 32768 + + Op.SSTORE(slot_code_worked, value_code_worked) + + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ) + ], + ), + container_post=Account(storage={slot_code_worked: value_code_worked}), + ) + + +def test_rjumpv_truncated( + eof_test: EOFTestFiller, +): + """EOF1I4200_0027 (Invalid) EOF code containing RJUMPV with max_index 0 but no immediates""" + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(1) + Op.RJUMPV + b"\0", + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ) + ], + ), + expect_exception=EOFException.TRUNCATED_INSTRUCTION, + ) + + +def test_rjumpv_truncated_1( + eof_test: EOFTestFiller, +): + """EOF1I4200_0028 (Invalid) EOF code containing truncated RJUMPV""" + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(1) + Op.RJUMPV, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ) + ], + ), + expect_exception=EOFException.TRUNCATED_INSTRUCTION, + ) + # - data: | + + +def test_rjumpv_truncated_2( + eof_test: EOFTestFiller, +): + """EOF1I4200_0029 (Invalid) EOF code containing truncated RJUMPV""" + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(1) + Op.RJUMPV + b"\0\0", + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ) + ], + ), + expect_exception=EOFException.TRUNCATED_INSTRUCTION, + ) + + +def test_rjumpv_truncated_3( + eof_test: EOFTestFiller, +): + """EOF1I4200_0030 (Invalid) EOF code containing truncated RJUMPV""" + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(1) + Op.RJUMPV + b"\0\0", + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ) + ], + ), + expect_exception=EOFException.TRUNCATED_INSTRUCTION, + ) + + +def test_rjumpv_truncated_4( + eof_test: EOFTestFiller, +): + """EOF code containing truncated RJUMPV table""" + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(1) + Op.RJUMPV + b"\2\0\0\0\0", + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ) + ], + ), + expect_exception=EOFException.TRUNCATED_INSTRUCTION, + ) + + +@pytest.mark.parametrize( + "table_size,invalid_index", + [ + pytest.param(1, 0, id="t1i0"), + pytest.param(256, 0, id="t256i0"), + pytest.param(256, 255, id="t256i255"), + ], +) +def test_rjumpv_into_header( + eof_test: EOFTestFiller, + table_size: int, + invalid_index: int, +): + """ + EOF1I4200_0031 (Invalid) EOF code containing RJUMPV with target outside code bounds + (Jumping into header) + """ + invalid_destination = -5 - (2 * table_size) + jump_table = [0 for _ in range(table_size)] + jump_table[invalid_index] = invalid_destination + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(1) + Op.RJUMPV[jump_table] + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ) + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +@pytest.mark.parametrize( + "table_size,invalid_index", + [ + pytest.param(1, 0, id="t1i0"), + pytest.param(256, 0, id="t256i0"), + pytest.param(256, 255, id="t256i255"), + ], +) +def test_rjumpv_before_container( + eof_test: EOFTestFiller, + table_size: int, + invalid_index: int, +): + """ + EOF1I4200_0032 (Invalid) EOF code containing RJUMPV with target outside code bounds + (Jumping to before code begin) + """ + invalid_destination = -13 - (2 * table_size) + jump_table = [0 for _ in range(table_size)] + jump_table[invalid_index] = invalid_destination + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(1) + Op.RJUMPV[jump_table] + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ) + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +@pytest.mark.parametrize( + "table_size,invalid_index", + [ + pytest.param(1, 0, id="t1i0"), + pytest.param(256, 0, id="t256i0"), + pytest.param(256, 255, id="t256i255"), + ], +) +def test_rjumpv_into_data( + eof_test: EOFTestFiller, + table_size: int, + invalid_index: int, +): + """ + EOF1I4200_0033 (Invalid) EOF code containing RJUMPV with target outside code bounds + (Jumping into data section) + """ + invalid_destination = 2 + jump_table = [0 for _ in range(table_size)] + jump_table[invalid_index] = invalid_destination + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(1) + Op.RJUMPV[jump_table] + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Data(data=b"\xaa\xbb\xcc"), + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +@pytest.mark.parametrize( + "table_size,invalid_index", + [ + pytest.param(1, 0, id="t1i0"), + pytest.param(256, 0, id="t256i0"), + pytest.param(256, 255, id="t256i255"), + ], +) +def test_rjumpv_after_container( + eof_test: EOFTestFiller, + table_size: int, + invalid_index: int, +): + """ + EOF1I4200_0034 (Invalid) EOF code containing RJUMPV with target outside code bounds + (Jumping to after code end) + """ + invalid_destination = 2 + jump_table = [0 for _ in range(table_size)] + jump_table[invalid_index] = invalid_destination + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(1) + Op.RJUMPV[jump_table] + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ) + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +@pytest.mark.parametrize( + "table_size,invalid_index", + [ + pytest.param(1, 0, id="t1i0"), + pytest.param(256, 0, id="t256i0"), + pytest.param(256, 255, id="t256i255"), + ], +) +def test_rjumpv_at_end( + eof_test: EOFTestFiller, + table_size: int, + invalid_index: int, +): + """ + EOF1I4200_0035 (Invalid) EOF code containing RJUMPV with target outside code bounds + (Jumping to code end) + """ + invalid_destination = 1 + jump_table = [0 for _ in range(table_size)] + jump_table[invalid_index] = invalid_destination + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(1) + Op.RJUMPV[jump_table] + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ) + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +@pytest.mark.parametrize( + "table_size,invalid_index", + [ + pytest.param(1, 0, id="t1i0"), + pytest.param(256, 0, id="t256i0"), + pytest.param(256, 255, id="t256i255"), + ], +) +@pytest.mark.parametrize( + "data_portion_end", + [True, False], + ids=["data_portion_end", "data_portion_start"], +) +def test_rjumpv_into_self( + eof_test: EOFTestFiller, + table_size: int, + invalid_index: int, + data_portion_end: bool, +): + """EOF1I4200_0036 (Invalid) EOF code containing RJUMPV with target same RJUMPV immediate""" + invalid_destination = -1 if data_portion_end else -(2 * table_size) - 1 + jump_table = [0 for _ in range(table_size)] + jump_table[invalid_index] = invalid_destination + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(1) + Op.RJUMPV[jump_table] + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ) + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +@pytest.mark.parametrize( + "table_size,invalid_index", + [ + pytest.param(1, 0, id="t1i0"), + pytest.param(256, 0, id="t256i0"), + pytest.param(256, 255, id="t256i255"), + ], +) +@pytest.mark.parametrize( + "data_portion_end", + [True, False], + ids=["data_portion_end", "data_portion_start"], +) +def test_rjumpv_into_rjump( + eof_test: EOFTestFiller, + table_size: int, + invalid_index: int, + data_portion_end: bool, +): + """EOF1I4200_0037 (Invalid) EOF code containing RJUMPV with target RJUMP immediate""" + invalid_destination = 3 if data_portion_end else 2 + jump_table = [0 for _ in range(table_size)] + jump_table[invalid_index] = invalid_destination + if table_size > 1: + valid_index = 0 + if valid_index == invalid_index: + valid_index += 1 + jump_table[valid_index] = 1 + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(1) + Op.RJUMPV[jump_table] + Op.STOP + Op.RJUMP[0] + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ) + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +@pytest.mark.parametrize( + "table_size,invalid_index", + [ + pytest.param(1, 0, id="t1i0"), + pytest.param(256, 0, id="t256i0"), + pytest.param(256, 255, id="t256i255"), + ], +) +@pytest.mark.parametrize( + "data_portion_end", + [True, False], + ids=["data_portion_end", "data_portion_start"], +) +def test_rjumpv_into_rjumpi( + eof_test: EOFTestFiller, + table_size: int, + invalid_index: int, + data_portion_end: bool, +): + """EOF1I4200_0038 (Invalid) EOF code containing RJUMPV with target RJUMPI immediate""" + invalid_destination = 5 if data_portion_end else 4 + jump_table = [0 for _ in range(table_size)] + jump_table[invalid_index] = invalid_destination + if table_size > 1: + valid_index = 0 + if valid_index == invalid_index: + valid_index += 1 + jump_table[valid_index] = 1 + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(1) + + Op.RJUMPV[jump_table] + + Op.STOP + + Op.PUSH1(1) + + Op.RJUMPI[0] + + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ) + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +@pytest.mark.parametrize( + "table_size,invalid_index", + [ + pytest.param(1, 0, id="t1i0"), + pytest.param(256, 0, id="t256i0"), + pytest.param(256, 255, id="t256i255"), + ], +) +@pytest.mark.parametrize("jump", [JumpDirection.FORWARD, JumpDirection.BACKWARD]) +def test_rjumpv_into_push_1( + eof_test: EOFTestFiller, + jump: JumpDirection, + table_size: int, + invalid_index: int, +): + """EOF1I4200_0039 (Invalid) EOF code containing RJUMPV with target PUSH1 immediate""" + if jump == JumpDirection.FORWARD: + invalid_destination = 2 + jump_table = [0 for _ in range(table_size)] + jump_table[invalid_index] = invalid_destination + code = ( + Op.PUSH1(1) + + Op.RJUMPV[jump_table] + + Op.STOP + + Op.PUSH1(1) + + Op.PUSH1(1) + + Op.SSTORE + + Op.STOP + ) + else: + invalid_destination = -(2 * table_size) - 3 + jump_table = [0 for _ in range(table_size)] + jump_table[invalid_index] = invalid_destination + code = Op.PUSH1(1) + Op.RJUMPV[jump_table] + Op.STOP + eof_test( + data=Container( + sections=[ + Section.Code( + code=code, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ) + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +@pytest.mark.parametrize( + "opcode", + [ + Op.PUSH2, + Op.PUSH3, + Op.PUSH4, + Op.PUSH5, + Op.PUSH6, + Op.PUSH7, + Op.PUSH8, + Op.PUSH9, + Op.PUSH10, + Op.PUSH11, + Op.PUSH12, + Op.PUSH13, + Op.PUSH14, + Op.PUSH15, + Op.PUSH16, + Op.PUSH17, + Op.PUSH18, + Op.PUSH19, + Op.PUSH20, + Op.PUSH21, + Op.PUSH22, + Op.PUSH23, + Op.PUSH24, + Op.PUSH25, + Op.PUSH26, + Op.PUSH27, + Op.PUSH28, + Op.PUSH29, + Op.PUSH30, + Op.PUSH31, + Op.PUSH32, + ], +) +@pytest.mark.parametrize( + "table_size,invalid_index", + [ + pytest.param(1, 0, id="t1i0"), + pytest.param(256, 0, id="t256i0"), + pytest.param(256, 255, id="t256i255"), + ], +) +@pytest.mark.parametrize( + "data_portion_end", + [True, False], + ids=["data_portion_end", "data_portion_start"], +) +@pytest.mark.parametrize("jump", [JumpDirection.FORWARD, JumpDirection.BACKWARD]) +def test_rjumpv_into_push_n( + eof_test: EOFTestFiller, + opcode: Op, + jump: JumpDirection, + table_size: int, + invalid_index: int, + data_portion_end: bool, +): + """EOF1I4200_0039 (Invalid) EOF code containing RJUMPV with target PUSH1 immediate""" + data_portion_length = int.from_bytes(opcode, byteorder="big") - 0x5F + if jump == JumpDirection.FORWARD: + invalid_destination = data_portion_length + 1 if data_portion_end else 2 + jump_table = [0 for _ in range(table_size)] + jump_table[invalid_index] = invalid_destination + code = ( + Op.PUSH1(1) + + Op.RJUMPV[jump_table] + + Op.STOP + + opcode[1] + + Op.PUSH1(1) + + Op.SSTORE + + Op.STOP + ) + else: + invalid_destination = ( + -(2 * table_size) - 3 + if data_portion_end + else -(2 * table_size) - 2 - data_portion_length + ) + jump_table = [0 for _ in range(table_size)] + jump_table[invalid_index] = invalid_destination + code = opcode[1] + Op.RJUMPV[jump_table] + Op.STOP + eof_test( + data=Container( + sections=[ + Section.Code( + code=code, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ) + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +@pytest.mark.parametrize( + "source_table_size,invalid_index", + [ + pytest.param(1, 0, id="s1i0"), + pytest.param(256, 0, id="s256i0"), + pytest.param(256, 255, id="s256i255"), + ], +) +@pytest.mark.parametrize("target_table_size", [1, 256], ids=["t1", "t256"]) +@pytest.mark.parametrize( + "data_portion_end", + [True, False], + ids=["data_portion_end", "data_portion_start"], +) +def test_rjumpv_into_rjumpv( + eof_test: EOFTestFiller, + source_table_size: int, + target_table_size: int, + invalid_index: int, + data_portion_end: bool, +): + """EOF1I4200_0040 (Invalid) EOF code containing RJUMPV with target other RJUMPV immediate""" + invalid_destination = 4 + (2 * target_table_size) if data_portion_end else 4 + source_jump_table = [0 for _ in range(source_table_size)] + source_jump_table[invalid_index] = invalid_destination + target_jump_table = [0 for _ in range(target_table_size)] + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(1) + + Op.RJUMPV[source_jump_table] + + Op.STOP + + Op.PUSH1(1) + + Op.RJUMPV[target_jump_table] + + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ) + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +@pytest.mark.parametrize( + "table_size,invalid_index", + [ + pytest.param(1, 0, id="t1i0"), + pytest.param(256, 0, id="t256i0"), + pytest.param(256, 255, id="t256i255"), + ], +) +@pytest.mark.parametrize( + "data_portion_end", + [True, False], + ids=["data_portion_end", "data_portion_start"], +) +def test_rjumpv_into_callf( + eof_test: EOFTestFiller, + table_size: int, + invalid_index: int, + data_portion_end: bool, +): + """EOF1I4200_0041 (Invalid) EOF code containing RJUMPV with target CALLF immediate""" + invalid_destination = 2 if data_portion_end else 1 + jump_table = [0 for _ in range(table_size)] + jump_table[invalid_index] = invalid_destination + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(0) + Op.RJUMPV[jump_table] + Op.CALLF[1] + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=1, + ), + Section.Code( + code=Op.SSTORE(1, 1) + Op.RETF, + code_outputs=0, + max_stack_height=2, + ), + ] + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +@pytest.mark.parametrize( + "table_size,invalid_index", + [ + pytest.param(1, 0, id="t1i0"), + pytest.param(256, 0, id="t256i0"), + pytest.param(256, 255, id="t256i255"), + ], +) +def test_rjumpv_into_dupn( + eof_test: EOFTestFiller, + table_size: int, + invalid_index: int, +): + """EOF code containing RJUMP with target DUPN immediate""" + invalid_destination = 1 + jump_table = [0 for _ in range(table_size)] + jump_table[invalid_index] = invalid_destination + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(1) + + Op.PUSH1(1) + + Op.PUSH1(0) + + Op.RJUMPV[jump_table] + + Op.DUPN[1] + + Op.SSTORE + + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=3, + ), + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +@pytest.mark.parametrize( + "table_size,invalid_index", + [ + pytest.param(1, 0, id="t1i0"), + pytest.param(256, 0, id="t256i0"), + pytest.param(256, 255, id="t256i255"), + ], +) +def test_rjumpv_into_swapn( + eof_test: EOFTestFiller, + table_size: int, + invalid_index: int, +): + """EOF code containing RJUMP with target SWAPN immediate""" + invalid_destination = 1 + jump_table = [0 for _ in range(table_size)] + jump_table[invalid_index] = invalid_destination + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(1) + + Op.PUSH1(1) + + Op.PUSH1(0) + + Op.RJUMPV[jump_table] + + Op.SWAPN[1] + + Op.SSTORE + + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=3, + ), + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +@pytest.mark.parametrize( + "table_size,invalid_index", + [ + pytest.param(1, 0, id="t1i0"), + pytest.param(256, 0, id="t256i0"), + pytest.param(256, 255, id="t256i255"), + ], +) +def test_rjump_into_exchange( + eof_test: EOFTestFiller, + table_size: int, + invalid_index: int, +): + """EOF code containing RJUMP with target EXCHANGE immediate""" + invalid_destination = 1 + jump_table = [0 for _ in range(table_size)] + jump_table[invalid_index] = invalid_destination + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(1) + + Op.PUSH1(2) + + Op.PUSH1(3) + + Op.PUSH1(0) + + Op.RJUMPV[1] + + Op.EXCHANGE[0x00] + + Op.SSTORE + + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=4, + ), + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +@pytest.mark.parametrize( + "table_size,invalid_index", + [ + pytest.param(1, 0, id="t1i0"), + pytest.param(256, 0, id="t256i0"), + pytest.param(256, 255, id="t256i255"), + ], +) +def test_rjumpv_into_eofcreate( + eof_test: EOFTestFiller, + table_size: int, + invalid_index: int, +): + """EOF code containing RJUMP with target EOFCREATE immediate""" + invalid_destination = 9 + jump_table = [0 for _ in range(table_size)] + jump_table[invalid_index] = invalid_destination + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.PUSH1(0) + + Op.RJUMPV[jump_table] + + Op.EOFCREATE[0](0, 0, 0, 0) + + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=4, + ), + Section.Container( + container=Container( + sections=[ + Section.Code( + code=Op.RETURNCONTRACT[0](0, 0), + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ), + Section.Container( + container=Container( + sections=[ + Section.Code( + code=Op.STOP, + code_outputs=NON_RETURNING_SECTION, + ) + ] + ) + ), + ] + ) + ), + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + ) + + +@pytest.mark.parametrize( + "table_size,invalid_index", + [ + pytest.param(1, 0, id="t1i0"), + pytest.param(256, 0, id="t256i0"), + pytest.param(256, 255, id="t256i255"), + ], +) +def test_rjumpv_into_returncontract( + eof_test: EOFTestFiller, + table_size: int, + invalid_index: int, +): + """EOF code containing RJUMP with target RETURNCONTRACT immediate""" + invalid_destination = 5 + jump_table = [0 for _ in range(table_size)] + jump_table[invalid_index] = invalid_destination + eof_test( + data=Container( + sections=[ + Section.Code( + code=Op.EOFCREATE[0](0, 0, 0, 0) + Op.STOP, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=4, + ), + Section.Container( + container=Container( + sections=[ + Section.Code( + code=Op.PUSH1(0) + + Op.RJUMPV[jump_table] + + Op.RETURNCONTRACT[0](0, 0), + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ), + Section.Container( + container=Container( + sections=[ + Section.Code( + code=Op.STOP, + code_outputs=NON_RETURNING_SECTION, + ) + ] + ) + ), + ] + ) + ), + ], + ), + expect_exception=EOFException.INVALID_RJUMP_DESTINATION, + )