|
2 | 2 |
|
3 | 3 | import itertools |
4 | 4 | from enum import Enum, auto, unique |
5 | | -from typing import Tuple |
| 5 | +from typing import Generator, Tuple |
6 | 6 |
|
7 | 7 | import pytest |
8 | 8 |
|
9 | 9 | from ethereum_test_exceptions.exceptions import EOFException |
10 | 10 | from ethereum_test_tools import EOFTestFiller |
11 | 11 | from ethereum_test_tools.eof.v1 import Container, Section |
12 | 12 | from ethereum_test_tools.vm.opcode import Opcodes as Op |
13 | | -from ethereum_test_types.eof.v1.constants import NON_RETURNING_SECTION |
| 13 | +from ethereum_test_types.eof.v1.constants import MAX_OPERAND_STACK_HEIGHT, NON_RETURNING_SECTION |
14 | 14 | from ethereum_test_vm.bytecode import Bytecode |
15 | 15 |
|
16 | 16 | from .. import EOF_FORK_NAME |
| 17 | +from ..eip3540_eof_v1.test_all_opcodes_in_container import valid_eof_opcodes |
17 | 18 |
|
18 | 19 | REFERENCE_SPEC_GIT_PATH = "EIPS/eip-5450.md" |
19 | 20 | REFERENCE_SPEC_VERSION = "f20b164b00ae5553f7536a6d7a83a0f254455e09" |
@@ -433,3 +434,54 @@ def test_rjumps_jumpf_nonreturning( |
433 | 434 | possible_exceptions.append(EOFException.INVALID_NON_RETURNING_FLAG) |
434 | 435 |
|
435 | 436 | eof_test(container=Container(sections=sections), expect_exception=possible_exceptions or None) |
| 437 | + |
| 438 | + |
| 439 | +def gen_stack_underflow_params() -> Generator[Tuple[Op, int], None, None]: |
| 440 | + """Generate parameters for stack underflow tests.""" |
| 441 | + for op in sorted(valid_eof_opcodes): |
| 442 | + if op.min_stack_height == 0: |
| 443 | + continue |
| 444 | + if op in (Op.EOFCREATE, Op.RETURNCODE): |
| 445 | + continue |
| 446 | + yield op, 0 |
| 447 | + if op.min_stack_height > 1: |
| 448 | + yield op, op.min_stack_height - 1 |
| 449 | + |
| 450 | + |
| 451 | +@pytest.mark.parametrize("spread", [0, 1, MAX_OPERAND_STACK_HEIGHT]) |
| 452 | +@pytest.mark.parametrize("op,stack_height", gen_stack_underflow_params()) |
| 453 | +def test_all_opcodes_variadic_stack_underflow( |
| 454 | + eof_test: EOFTestFiller, op: Op, stack_height: int, spread: int |
| 455 | +): |
| 456 | + """ |
| 457 | + Test EOF validation failing due to stack overflow |
| 458 | + caused by the specific instruction `op`. |
| 459 | + There is similar non-variadic test variant in test_all_opcodes_stack_underflow(). |
| 460 | + """ |
| 461 | + code = Bytecode() |
| 462 | + |
| 463 | + # Check if the op increases the stack height (e.g. DUP instructions). |
| 464 | + # We need to leave space for this increase not to cause stack overflow. |
| 465 | + stack_height_increase = max(op.pushed_stack_items - op.popped_stack_items, 0) |
| 466 | + # Cap the spread if it would exceed the maximum stack height. |
| 467 | + spread = min(spread, MAX_OPERAND_STACK_HEIGHT - (stack_height + stack_height_increase)) |
| 468 | + # Create a range stack height of 0-spread. |
| 469 | + code = Op.RJUMPI[spread](Op.CALLVALUE) + Op.PUSH0 * spread |
| 470 | + |
| 471 | + # Create the desired stack height. |
| 472 | + code += Op.PUSH0 * stack_height |
| 473 | + |
| 474 | + if op.has_data_portion(): |
| 475 | + code += op[0] # produce required imm bytes |
| 476 | + else: |
| 477 | + code += op |
| 478 | + |
| 479 | + if not op.terminating: |
| 480 | + code += Op.STOP |
| 481 | + |
| 482 | + eof_test( |
| 483 | + container=Container( |
| 484 | + sections=[Section.Code(code)], |
| 485 | + validity_error=EOFException.STACK_UNDERFLOW, |
| 486 | + ) |
| 487 | + ) |
0 commit comments