Skip to content

Commit

Permalink
new(test): add tests for EOF/EIP-663 DUPN SWAPN (ethereum#502)
Browse files Browse the repository at this point in the history
* new(test): add tests for EOF/EIP-663 DUPN SWAPN

* new(fw): EOF stack exceptions

* fix(tests): eip-7692: spec sha

* new(tests): eip-7692: dupn negative tests

* new(tests): eip-7692: parametrize

* new(tests): eip-7692: more swapn tests

* docs: changelog

---------

Co-authored-by: Mario Vega <[email protected]>
  • Loading branch information
chfast and marioevz authored May 23, 2024
1 parent 56a8249 commit fd7361a
Show file tree
Hide file tree
Showing 9 changed files with 323 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Test fixtures for use by clients are available for each release on the [Github r

- ✨ 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)).
-[EIP-663](https://eips.ethereum.org/EIPS/eip-663): Add `test_dupn.py` and `test_swapn.py` ([#502](https://github.com/ethereum/execution-spec-tests/pull/502)).

### 🛠️ Framework

Expand Down
5 changes: 5 additions & 0 deletions src/ethereum_test_tools/exceptions/evmone_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ class EvmoneExceptionMapper:
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"),
ExceptionMessage(EOFException.STACK_UNDERFLOW, "err: stack_underflow"),
ExceptionMessage(
EOFException.MAX_STACK_HEIGHT_ABOVE_LIMIT, "err: max_stack_height_above_limit"
),
ExceptionMessage(EOFException.INVALID_MAX_STACK_HEIGHT, "err: invalid_max_stack_height"),
ExceptionMessage(EOFException.INVALID_DATALOADN_INDEX, "err: invalid_dataloadn_index"),
)

Expand Down
12 changes: 12 additions & 0 deletions src/ethereum_test_tools/exceptions/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,18 @@ class EOFException(ExceptionBase):
"""
EOF container's body have code sections that are unreachable
"""
STACK_UNDERFLOW = auto()
"""
EOF container's code produces an stack underflow
"""
MAX_STACK_HEIGHT_ABOVE_LIMIT = auto()
"""
EOF container's specified max stack height is above the limit
"""
INVALID_MAX_STACK_HEIGHT = auto()
"""
EOF container section's specified max stack height does not match the actual stack height
"""
INVALID_DATALOADN_INDEX = auto()
"""
A DATALOADN instruction has out-of-bounds index for the data section
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""
abstract: Tests [EIP-663: SWAPN, DUPN and EXCHANGE instructions](https://eips.ethereum.org/EIPS/eip-663)
Tests for [EIP-663: SWAPN, DUPN and EXCHANGE instructions](https://eips.ethereum.org/EIPS/eip-663).
""" # noqa: E501

REFERENCE_SPEC_GIT_PATH = "EIPS/eip-663.md"
REFERENCE_SPEC_VERSION = "b658bb87fe039d29e9475d5cfaebca9b92e0fca2"
14 changes: 14 additions & 0 deletions tests/prague/eip7692_eof_v1/eip663_dupn_swapn_exchange/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""
Pytest fixtures for EIP-663 tests
"""
import pytest

from ethereum_test_tools import Transaction


@pytest.fixture
def tx() -> Transaction:
"""
Produces the default Transaction.
"""
return Transaction(to=0xC0DE, gas_limit=10_000_000)
139 changes: 139 additions & 0 deletions tests/prague/eip7692_eof_v1/eip663_dupn_swapn_exchange/test_dupn.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
"""
abstract: Tests [EIP-663: SWAPN, DUPN and EXCHANGE instructions](https://eips.ethereum.org/EIPS/eip-663)
Tests for the DUPN instruction.
""" # noqa: E501

import pytest

from ethereum_test_tools import (
Account,
Environment,
EOFException,
EOFTestFiller,
StateTestFiller,
TestAddress,
Transaction,
)
from ethereum_test_tools.eof.v1 import Container, Section
from ethereum_test_tools.eof.v1.constants import MAX_OPERAND_STACK_HEIGHT, NON_RETURNING_SECTION
from ethereum_test_tools.vm.opcode import Opcodes as Op

from ..eip3540_eof_v1.spec import EOF_FORK_NAME
from . import REFERENCE_SPEC_GIT_PATH, REFERENCE_SPEC_VERSION

REFERENCE_SPEC_GIT_PATH = REFERENCE_SPEC_GIT_PATH
REFERENCE_SPEC_VERSION = REFERENCE_SPEC_VERSION


@pytest.mark.valid_from(EOF_FORK_NAME)
def test_dupn_all_valid_immediates(
tx: Transaction,
state_test: StateTestFiller,
):
"""
Test case for all valid DUPN immediates.
"""
n = 2**8
values = range(0xD00, 0xD00 + n)

eof_code = Container(
sections=[
Section.Code(
code=b"".join(Op.PUSH2(v) for v in values)
+ b"".join(Op.SSTORE(x, Op.DUPN[x]) for x in range(0, n))
+ Op.STOP,
code_inputs=0,
code_outputs=NON_RETURNING_SECTION,
max_stack_height=n + 2,
)
],
)

pre = {
TestAddress: Account(balance=1_000_000_000),
tx.to: Account(code=eof_code),
}

post = {tx.to: Account(storage=dict(zip(range(0, n), reversed(values))))}

state_test(
env=Environment(),
pre=pre,
post=post,
tx=tx,
)


@pytest.mark.parametrize(
"stack_height,max_stack_height",
[
[0, 0],
[0, 1],
[1, 1],
[1, 2],
[2**8 - 1, 2**8 - 1],
[2**8 - 1, 2**8],
],
)
@pytest.mark.valid_from(EOF_FORK_NAME)
def test_dupn_stack_underflow(
stack_height: int,
max_stack_height: int,
eof_test: EOFTestFiller,
):
"""
Test case out of bounds DUPN immediate.
"""
eof_code = Container(
sections=[
Section.Code(
code=b"".join(Op.PUSH2(v) for v in range(0, stack_height))
+ Op.DUPN[stack_height]
+ Op.STOP,
code_inputs=0,
code_outputs=NON_RETURNING_SECTION,
max_stack_height=max_stack_height,
)
],
)
eof_test(
data=eof_code,
expect_exception=EOFException.STACK_UNDERFLOW,
)


@pytest.mark.parametrize(
"dupn_operand,max_stack_height,expect_exception",
[
[0, MAX_OPERAND_STACK_HEIGHT, EOFException.INVALID_MAX_STACK_HEIGHT],
[0, MAX_OPERAND_STACK_HEIGHT + 1, EOFException.MAX_STACK_HEIGHT_ABOVE_LIMIT],
[2**8 - 1, MAX_OPERAND_STACK_HEIGHT, EOFException.INVALID_MAX_STACK_HEIGHT],
[2**8 - 1, MAX_OPERAND_STACK_HEIGHT + 1, EOFException.MAX_STACK_HEIGHT_ABOVE_LIMIT],
],
)
@pytest.mark.valid_from(EOF_FORK_NAME)
def test_dupn_stack_overflow(
dupn_operand: int,
max_stack_height: int,
expect_exception: EOFException,
eof_test: EOFTestFiller,
):
"""
Test case where DUPN produces an stack overflow.
"""
eof_code = Container(
sections=[
Section.Code(
code=b"".join(Op.PUSH2(v) for v in range(0, MAX_OPERAND_STACK_HEIGHT))
+ Op.DUPN[dupn_operand]
+ Op.STOP,
code_inputs=0,
code_outputs=NON_RETURNING_SECTION,
max_stack_height=max_stack_height,
)
],
)
eof_test(
data=eof_code,
expect_exception=expect_exception,
)
131 changes: 131 additions & 0 deletions tests/prague/eip7692_eof_v1/eip663_dupn_swapn_exchange/test_swapn.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
"""
abstract: Tests [EIP-663: SWAPN, DUPN and EXCHANGE instructions](https://eips.ethereum.org/EIPS/eip-663)
Tests for the SWAPN instruction.
""" # noqa: E501

import pytest

from ethereum_test_tools import (
Account,
Environment,
EOFException,
EOFTestFiller,
StateTestFiller,
TestAddress,
Transaction,
)
from ethereum_test_tools.eof.v1 import Container, Section
from ethereum_test_tools.eof.v1.constants import MAX_OPERAND_STACK_HEIGHT, NON_RETURNING_SECTION
from ethereum_test_tools.vm.opcode import Opcodes as Op

from ..eip3540_eof_v1.spec import EOF_FORK_NAME
from . import REFERENCE_SPEC_GIT_PATH, REFERENCE_SPEC_VERSION

REFERENCE_SPEC_GIT_PATH = REFERENCE_SPEC_GIT_PATH
REFERENCE_SPEC_VERSION = REFERENCE_SPEC_VERSION


@pytest.mark.valid_from(EOF_FORK_NAME)
def test_swapn_all_valid_immediates(
tx: Transaction,
state_test: StateTestFiller,
):
"""
Test case for all valid SWAPN immediates.
"""
n = 256
values = range(0x500, 0x500 + 257)

eof_code = Container(
sections=[
Section.Code(
code=b"".join(Op.PUSH2(v) for v in values)
+ b"".join(Op.SSTORE(x, Op.SWAPN[0xFF - x]) for x in range(0, n))
+ Op.STOP,
code_inputs=0,
code_outputs=NON_RETURNING_SECTION,
max_stack_height=n + 2,
)
],
)

pre = {
TestAddress: Account(balance=1_000_000_000),
tx.to: Account(code=eof_code),
}

values_rotated = list(values[1:]) + [values[0]]
post = {tx.to: Account(storage=dict(zip(range(0, n), reversed(values_rotated))))}

state_test(
env=Environment(),
pre=pre,
post=post,
tx=tx,
)


@pytest.mark.parametrize(
"swapn_operand",
[
0,
2**8 - 1,
],
)
@pytest.mark.valid_from(EOF_FORK_NAME)
def test_swapn_on_max_stack(
swapn_operand: int,
eof_test: EOFTestFiller,
):
"""
Test case out of bounds DUPN immediate.
"""
eof_code = Container(
sections=[
Section.Code(
code=b"".join(Op.PUSH2(v) for v in range(0, MAX_OPERAND_STACK_HEIGHT))
+ Op.SWAPN[swapn_operand]
+ Op.STOP,
code_inputs=0,
code_outputs=NON_RETURNING_SECTION,
max_stack_height=MAX_OPERAND_STACK_HEIGHT,
)
],
)
eof_test(
data=eof_code,
)


@pytest.mark.parametrize(
"stack_height",
[
0,
1,
2**8 - 1,
],
)
@pytest.mark.valid_from(EOF_FORK_NAME)
def test_swapn_stack_underflow(
stack_height: int,
eof_test: EOFTestFiller,
):
"""
Test case out of bounds DUPN immediate.
"""
eof_code = Container(
sections=[
Section.Code(
code=b"".join(Op.PUSH2(v) for v in range(0, stack_height))
+ Op.SWAPN[stack_height]
+ Op.STOP,
code_inputs=0,
code_outputs=NON_RETURNING_SECTION,
max_stack_height=MAX_OPERAND_STACK_HEIGHT,
)
],
)
eof_test(
data=eof_code,
expect_exception=EOFException.STACK_UNDERFLOW,
)
12 changes: 12 additions & 0 deletions tests/prague/eip7692_eof_v1/tracker.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,18 @@

## EIP-663: SWAPN, DUPN and EXCHANGE instructions

### Validation

- [ ] A DUPN instruction causes stack overflow
- [ ] A DUPN instruction causes stack underflow
- [ ] A DUPN instruction causes max stack height mismatch
- [ ] A SWAPN instruction causes stack underflow

### Execution

- [x] Positive tests for DUPN instructions (./eip663_dupn_swapn_exchange/test_dupn.py::test_dupn_all_valid_immediates)
- [x] Positive tests for SWAPN instructions (./eip663_dupn_swapn_exchange/test_swapn.py::test_swapn_all_valid_immediates)

## EIP-7069: Revamped CALL instructions

## EIP-7620: EOF Contract Creation
Expand Down
2 changes: 2 additions & 0 deletions whitelist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ dup
dunder
EEST
eip
eip3540
eips
EIPs
endianness
Expand Down Expand Up @@ -163,6 +164,7 @@ hyperledger
iat
ignoreRevsFile
img
immediates
incrementing
init
initcode
Expand Down

0 comments on commit fd7361a

Please sign in to comment.