Skip to content

test_end_of_code_stack_underflow()

Documentation for tests/amsterdam/eip8024_dupn_swapn_exchange/test_endofcode_underflow.py::test_end_of_code_stack_underflow@5c024cbb.

Generate fixtures for these test cases for Amsterdam with:

fill -v tests/amsterdam/eip8024_dupn_swapn_exchange/test_endofcode_underflow.py::test_end_of_code_stack_underflow --fork Amsterdam

Test EIP-8024 opcodes at end of code with one fewer stack item than required by the zero-decoded immediate.

Per EIP-8024, code[pc + 1] evaluates to 0 when beyond end of code. The opcode must still execute and, with insufficient stack depth for the decoded immediate, must halt with stack underflow.

A buggy implementation that treats the missing immediate as an implicit STOP would incorrectly succeed and persist the marker stored before the opcode.

Source code in tests/amsterdam/eip8024_dupn_swapn_exchange/test_endofcode_underflow.py
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
@pytest.mark.parametrize(
    "eip8024_opcode,pushed_items",
    [
        # DUPN: decode_single(0) = 145, needs 145 items — push 144.
        pytest.param(Op.DUPN, 144, id="dupn"),
        # SWAPN: decode_single(0) = 145, needs 146 items — push 145.
        pytest.param(Op.SWAPN, 145, id="swapn"),
        # EXCHANGE: decode_pair(0) = (9, 16), needs 17 items — push 16.
        pytest.param(Op.EXCHANGE, 16, id="exchange"),
    ],
)
def test_end_of_code_stack_underflow(
    pre: Alloc,
    state_test: StateTestFiller,
    eip8024_opcode: Op,
    pushed_items: int,
) -> None:
    """
    Test EIP-8024 opcodes at end of code with one fewer stack item than
    required by the zero-decoded immediate.

    Per EIP-8024, `code[pc + 1]` evaluates to `0` when beyond end of code.
    The opcode must still execute and, with insufficient stack depth for
    the decoded immediate, must halt with stack underflow.

    A buggy implementation that treats the missing immediate as an
    implicit STOP would incorrectly succeed and persist the marker
    stored before the opcode.
    """
    sender = pre.fund_eoa()
    marker_value = 0x42

    code = (
        # store marker that must not persist if the opcode underflows
        Op.SSTORE(0, marker_value)
        # one fewer item than the zero-decoded immediate requires
        + Op.PUSH0 * pushed_items
        # end-of-code EIP-8024 opcode, no immediate byte
        + eip8024_opcode
    )
    contract_address = pre.deploy_contract(code=code)

    tx = Transaction(to=contract_address, sender=sender)

    # Transaction must fail (stack underflow), leaving storage untouched.
    post = {contract_address: Account(storage={})}
    state_test(pre=pre, post=post, tx=tx)

Parametrized Test Cases

This test generates 3 parametrized test cases across 1 fork.