Skip to content

Commit

Permalink
new(tests): EOF - EIP-3540: validation of opcodes (#932)
Browse files Browse the repository at this point in the history
Add missing opcode tests:
- invalid opcode placed after a terminating instruction,
- opcodes with truncated immediate bytes.

Mark rest of the "opcode tests" as done by providing links to tests.

Co-authored-by: Mario Vega <[email protected]>
Co-authored-by: danceratopz <[email protected]>
  • Loading branch information
3 people authored Nov 7, 2024
1 parent ec65b3b commit fe37ca2
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 7 deletions.
4 changes: 4 additions & 0 deletions converted-ethereum-tests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@ GeneralStateTests/stCreate2/call_then_create2_successful_then_returndatasize.jso
([#598](https://github.com/ethereum/execution-spec-tests/pull/598))
EOFTests/EIP3540/validInvalid.json

EOFTests/EIP3670/validInvalid.json
EOFTests/efValidation/EOF1_embedded_container_.json
EOFTests/efValidation/EOF1_eofcreate_valid_.json
EOFTests/efValidation/EOF1_section_order_.json
EOFTests/efValidation/EOF1_truncated_section_.json
EOFTests/efValidation/EOF1_undefined_opcodes_.json
EOFTests/efValidation/EOF1_truncated_push_.json
EOFTests/efValidation/deprecated_instructions_.json
EOFTests/efValidation/unreachable_code_sections_.json

([#647](https://github.com/ethereum/execution-spec-tests/pull/647))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"""
EOF Container: check how every opcode behaves in the middle of the valid eof container code
"""
from typing import Any, Dict, List
import itertools
from typing import Any, Dict, Generator, List, Tuple

import pytest

Expand All @@ -10,6 +11,7 @@
from ethereum_test_tools import UndefinedOpcodes
from ethereum_test_tools.eof.v1 import Container, ContainerKind, Section
from ethereum_test_tools.eof.v1.constants import MAX_OPERAND_STACK_HEIGHT
from ethereum_test_vm import Bytecode

from .. import EOF_FORK_NAME

Expand Down Expand Up @@ -58,6 +60,8 @@
Op.JUMPF,
}

data_portion_opcodes = {op for op in all_opcodes if op.has_data_portion()}


# NOTE: `sorted` is used to ensure that the tests are collected in a deterministic order.

Expand Down Expand Up @@ -118,6 +122,52 @@ def test_all_opcodes_in_container(
)


@pytest.mark.parametrize(
"opcode",
sorted(invalid_eof_opcodes | undefined_opcodes),
)
@pytest.mark.parametrize(
"terminating_opcode",
sorted(halting_opcodes) + [Op.RJUMP],
)
def test_invalid_opcodes_after_stop(
eof_test: EOFTestFiller,
opcode: Opcode,
terminating_opcode: Opcode,
):
"""
Test that an invalid opcode placed after STOP (terminating instruction) invalidates EOF.
"""
terminating_code = Bytecode(terminating_opcode)
match terminating_opcode: # Enhance the code for complex opcodes.
case Op.RETURNCONTRACT:
terminating_code = Op.RETURNCONTRACT[0]
case Op.RETURN | Op.REVERT:
terminating_code = Op.PUSH0 + Op.PUSH0 + terminating_opcode
case Op.RJUMP:
terminating_code = Op.RJUMP[-3]

eof_code = Container(
kind=ContainerKind.INITCODE
if terminating_opcode == Op.RETURNCONTRACT
else ContainerKind.RUNTIME,
sections=[
Section.Code(code=terminating_code + opcode),
Section.Data("00" * 32),
]
+ (
[Section.Container(container=Container.Code(Op.INVALID))]
if terminating_opcode == Op.RETURNCONTRACT
else []
),
)

eof_test(
data=eof_code,
expect_exception=EOFException.UNDEFINED_INSTRUCTION,
)


@pytest.mark.parametrize(
"opcode",
sorted(
Expand Down Expand Up @@ -382,3 +432,67 @@ def test_all_opcodes_stack_overflow(
data=eof_code,
expect_exception=exception,
)


def valid_opcode_combinations(
compute_max_stack_height_options: List[bool],
truncate_all_options: List[bool],
opcodes: List[Opcode],
) -> Generator[Tuple[bool, bool, Opcode], None, None]:
"""
Create valid parameter combinations for test_truncated_data_portion_opcodes().
"""
for opcode, truncate_all, compute_max_stack_height in itertools.product(
opcodes, truncate_all_options, compute_max_stack_height_options
):
opcode_with_data_portion: bytes = bytes(opcode[1])

# Skip invalid or redundant combinations to avoid using pytest.skip in the test
if len(opcode_with_data_portion) == 2 and truncate_all:
continue
if (
compute_max_stack_height
and max(opcode.min_stack_height, opcode.pushed_stack_items) == 0
):
continue

yield compute_max_stack_height, truncate_all, opcode


@pytest.mark.parametrize(
"compute_max_stack_height, truncate_all, opcode",
valid_opcode_combinations([False, True], [False, True], sorted(data_portion_opcodes)),
)
def test_truncated_data_portion_opcodes(
eof_test: EOFTestFiller,
opcode: Opcode,
truncate_all: bool,
compute_max_stack_height: bool,
):
"""
Test that an instruction with data portion and truncated immediate bytes
(therefore a terminating instruction is also missing) invalidates EOF.
"""
opcode_with_data_portion: bytes = bytes(opcode[1])

# Compose instruction bytes with empty imm bytes (truncate_all) or 1 byte shorter imm bytes.
opcode_bytes = opcode_with_data_portion[0:1] if truncate_all else opcode_with_data_portion[:-1]

if opcode.min_stack_height > 0:
opcode_bytes = bytes(Op.PUSH0 * opcode.min_stack_height) + opcode_bytes

max_stack_height = (
max(opcode.min_stack_height, opcode.pushed_stack_items) if compute_max_stack_height else 0
)

eof_code = Container(
sections=[
Section.Code(opcode_bytes, max_stack_height=max_stack_height),
# Provide data section potentially confused with missing imm bytes.
Section.Data(b"\0" * 64),
]
)
eof_test(
data=eof_code,
expect_exception=EOFException.TRUNCATED_INSTRUCTION,
)
12 changes: 6 additions & 6 deletions tests/osaka/eip7692_eof_v1/eof_tracker.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,12 @@

### Validation

- [ ] Code section with invalid opcodes is rejected (ethereum/tests: ./src/EOFTestsFiller/efExample/validInvalidFiller.yml src/EOFTestsFiller/efValidation/EOF1_undefined_opcodes_Copier.json src/EOFTestsFiller/EIP3670/validInvalidFiller.yml)
- [ ] INVALID opcode is valid (ethereum/tests: ./src/EOFTestsFiller/efExample/validInvalidFiller.yml)
- [ ] Truncated PUSH data (ethereum/tests: ./src/EOFTestsFiller/efExample/validInvalidFiller.yml src/EOFTestsFiller/efValidation/EOF1_truncated_push_Copier.json src/EOFTestsFiller/EIP3670/validInvalidFiller.yml)
- [ ] Opcodes deprecated in EOF are rejected (ethereum/tests: src/EOFTestsFiller/efValidation/deprecated_instructions_Copier.json ethereum/tests: src/EOFTestsFiller/EIP3670/validInvalidFiller.yml)
- [ ] Codes with each valid opcodes (ethereum/tests: src/EOFTestsFiller/EIP3670/validInvalidFiller.yml)
- [ ] Undefined instruction after terminating instruction (ethereum/tests: src/EOFTestsFiller/EIP3670/validInvalidFiller.yml)
- [x] Code section with invalid opcodes is rejected ([`tests/osaka/eip7692_eof_v1/eip3540_eof_v1/test_all_opcodes_in_container.py::test_all_opcodes_in_container`](./eip3540_eof_v1/test_all_opcodes_in_container/test_all_opcodes_in_container.md))
- [x] INVALID opcode is valid ([`tests/osaka/eip7692_eof_v1/eip3540_eof_v1/test_all_opcodes_in_container.py::test_all_opcodes_in_container`](./eip3540_eof_v1/test_all_opcodes_in_container/test_all_opcodes_in_container.md))
- [x] Truncated PUSH data ([`tests/osaka/eip7692_eof_v1/eip3540_eof_v1/test_all_opcodes_in_container.py::test_truncated_data_portion_opcodes`](./eip3540_eof_v1/test_all_opcodes_in_container/test_truncated_data_portion_opcodes.md))
- [x] Opcodes deprecated in EOF are rejected ([`tests/osaka/eip7692_eof_v1/eip3540_eof_v1/test_all_opcodes_in_container.py::test_all_opcodes_in_container`](./eip3540_eof_v1/test_all_opcodes_in_container/test_all_opcodes_in_container.md))
- [x] Codes with each valid opcodes ([`tests/osaka/eip7692_eof_v1/eip3540_eof_v1/test_all_opcodes_in_container.py::test_all_opcodes_in_container`](./eip3540_eof_v1/test_all_opcodes_in_container/test_all_opcodes_in_container.md))
- [x] Undefined instruction after terminating instruction ([`tests/osaka/eip7692_eof_v1/eip3540_eof_v1/test_all_opcodes_in_container.py::test_invalid_opcodes_after_stop`](./eip3540_eof_v1/test_all_opcodes_in_container/test_invalid_opcodes_after_stop.md))

## EIP-4200: EOF - Static relative jumps

Expand Down

0 comments on commit fe37ca2

Please sign in to comment.