Skip to content

Commit

Permalink
new(test) EOFCREATE and RETURNCONTRACT opcode validation tests (ether…
Browse files Browse the repository at this point in the history
…eum#629)

Test 3 rules

- Opcodes in subcontainers, by usage kind (EOFCREATE/RETURNCONTRACT)
- Mixed reference container (both EOFCREATE and RETURNCONTRACT)
- No orphan containers
  • Loading branch information
shemnon authored Jun 27, 2024
2 parents 7955001 + af71891 commit f514b68
Show file tree
Hide file tree
Showing 6 changed files with 285 additions and 29 deletions.
1 change: 1 addition & 0 deletions src/ethereum_test_tools/exceptions/evmone_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ class EvmoneExceptionMapper:
ExceptionMessage(
EOFException.TOPLEVEL_CONTAINER_TRUNCATED, "err: toplevel_container_truncated"
),
ExceptionMessage(EOFException.ORPHAN_SUBCONTAINER, "err: unreferenced_subcontainer"),
)

def __init__(self) -> None:
Expand Down
4 changes: 4 additions & 0 deletions src/ethereum_test_tools/exceptions/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,10 @@ class EOFException(ExceptionBase):
"""
Top-level EOF container has data section truncated
"""
ORPHAN_SUBCONTAINER = auto()
"""
EOF container has an unreferenced subcontainer.
'"""


"""
Expand Down
32 changes: 20 additions & 12 deletions src/ethereum_test_tools/spec/eof/eof_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,14 +165,14 @@ def check_container_exception(cls, data: Any) -> Any:
)
if expect_exception is None:
data["expect_exception"] = container.validity_error
if "container_kind" in container.model_fields_set:
if "kind" in container.model_fields_set:
if container_kind is not None:
assert str(container.kind) == container_kind, (
assert container.kind == container_kind, (
f"Container kind type {str(container.kind)} "
f"does not match test {container_kind}."
)
if container.kind != ContainerKind.RUNTIME:
data["container_kind"] = str(container.kind)
data["container_kind"] = container.kind
return data

@classmethod
Expand Down Expand Up @@ -280,6 +280,7 @@ class EOFStateTest(EOFTest):
Filler type that tests EOF containers and also generates a state/blockchain test.
"""

deploy_tx: bool = False
tx_gas_limit: int = 10_000_000
tx_data: Bytes = Bytes(b"")
tx_sender_funding_amount: int = 1_000_000_000_000_000_000_000
Expand All @@ -306,16 +307,23 @@ def generate_state_test(self) -> StateTest:
Generate the StateTest filler.
"""
assert self.pre is not None, "pre must be set to generate a StateTest."
container_address = self.pre.deploy_contract(code=self.data)
sender = self.pre.fund_eoa(amount=self.tx_sender_funding_amount)
tx = Transaction(
to=container_address,
gas_limit=self.tx_gas_limit,
gas_price=10,
protected=False,
data=self.tx_data,
sender=sender,
)
if self.deploy_tx:
tx = Transaction(
to=None,
gas_limit=self.tx_gas_limit,
data=self.data + self.tx_data,
sender=sender,
)
container_address = tx.created_contract
else:
container_address = self.pre.deploy_contract(code=self.data)
tx = Transaction(
to=container_address,
gas_limit=self.tx_gas_limit,
data=self.tx_data,
sender=sender,
)
post = Alloc()
post[container_address] = self.container_post
return StateTest(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,6 @@ def test_eofcreate_in_initcode_reverts(
+ Op.REVERT(0, 0),
),
Section.Container(container=smallest_initcode_subcontainer),
Section.Container(container=smallest_runtime_subcontainer),
]
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ def test_initcode_aborts(
Size of the factory portion of test_eofcreate_deploy_sizes, but as the runtime code is dynamic, we
have to use a pre-calculated size
"""
factory_size = 30
factory_size = 74


@pytest.mark.parametrize(
Expand Down Expand Up @@ -194,31 +194,33 @@ def test_eofcreate_deploy_sizes(
initcode_subcontainer = Container(
name="Initcode Subcontainer",
sections=[
Section.Code(code=Op.RETURNCONTRACT[0](0, 0)),
Section.Code(
code=Op.RETURNCONTRACT[0](0, 0),
),
Section.Container(container=runtime_container),
],
)

factory_container = Container(
sections=[
Section.Code(
code=Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, 0))
+ Op.SSTORE(slot_code_worked, value_code_worked)
+ Op.STOP,
),
Section.Container(container=initcode_subcontainer),
]
)

assert factory_size == (
len(initcode_subcontainer) - len(runtime_container)
len(factory_container) - len(runtime_container)
), "factory_size is wrong, expected factory_size is %d, calculated is %d" % (
factory_size,
len(initcode_subcontainer),
len(factory_container),
)

sender = pre.fund_eoa()
contract_address = pre.deploy_contract(
code=Container(
sections=[
Section.Code(
code=Op.SSTORE(slot_create_address, Op.EOFCREATE[0](0, 0, 0, 0))
+ Op.SSTORE(slot_code_worked, value_code_worked)
+ Op.STOP,
),
Section.Container(container=initcode_subcontainer),
]
)
)
contract_address = pre.deploy_contract(code=factory_container)
# Storage in 0 should have the address,
# Storage 1 is a canary of 1 to make sure it tried to execute, which also covers cases of
# data+code being greater than initcode_size_max, which is allowed.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
"""
EOF Subcontainer tests covering simple cases.
"""
import pytest

from ethereum_test_tools import Account, EOFException, EOFStateTestFiller, EOFTestFiller
from ethereum_test_tools.eof.v1 import Container, ContainerKind, Section
from ethereum_test_tools.vm.opcode import Opcodes as Op

from .. import EOF_FORK_NAME
from .helpers import slot_code_worked, value_code_worked

REFERENCE_SPEC_GIT_PATH = "EIPS/eip-7620.md"
REFERENCE_SPEC_VERSION = "52ddbcdddcf72dd72427c319f2beddeb468e1737"

pytestmark = pytest.mark.valid_from(EOF_FORK_NAME)

eofcreate_code_section = Section.Code(
code=Op.EOFCREATE[0](0, 0, 0, 0) + Op.SSTORE(slot_code_worked, value_code_worked) + Op.STOP,
max_stack_height=4,
)
returncontract_code_section = Section.Code(
code=Op.SSTORE(slot_code_worked, value_code_worked) + Op.RETURNCONTRACT[0](0, 0),
max_stack_height=2,
)
stop_sub_container = Section.Container(container=Container(sections=[Section.Code(code=Op.STOP)]))
return_sub_container = Section.Container(
container=Container(sections=[Section.Code(code=Op.RETURN(0, 0), max_stack_height=2)])
)
revert_sub_container = Section.Container(
container=Container(sections=[Section.Code(code=Op.REVERT(0, 0), max_stack_height=2)])
)
returncontract_sub_container = Section.Container(
container=Container(
sections=[
Section.Code(
code=Op.RETURNCONTRACT[0](0, 0),
max_stack_height=2,
),
stop_sub_container,
],
)
)


def test_simple_create_from_deployed(
eof_state_test: EOFStateTestFiller,
):
"""Simple EOF creation from a deployed EOF container"""
eof_state_test(
data=Container(
sections=[
eofcreate_code_section,
returncontract_sub_container,
],
),
container_post=Account(storage={slot_code_worked: value_code_worked}),
)


def test_simple_create_from_creation(
eof_state_test: EOFStateTestFiller,
):
"""Simple EOF creation from a create transaction container"""
eof_state_test(
deploy_tx=True,
data=Container(
sections=[
returncontract_code_section,
stop_sub_container,
],
),
container_post=Account(storage={slot_code_worked: value_code_worked}),
)


@pytest.mark.parametrize(
"zero_section",
[eofcreate_code_section, returncontract_code_section],
ids=["eofcreate", "returncontract"],
)
def test_reverting_container(
eof_state_test: EOFStateTestFiller,
zero_section: Container,
):
"""Test revert containers"""
eof_state_test(
deploy_tx=zero_section == returncontract_code_section,
data=Container(
sections=[
zero_section,
revert_sub_container,
],
),
container_post=Account(storage={slot_code_worked: value_code_worked}),
)


@pytest.mark.parametrize(
"code_section,first_sub_container",
[
(eofcreate_code_section, returncontract_sub_container),
(returncontract_code_section, stop_sub_container),
],
ids=["eofcreate", "returncontract"],
)
@pytest.mark.parametrize(
"extra_sub_container",
[stop_sub_container, revert_sub_container, returncontract_sub_container],
ids=["stop", "revert", "returncontract"],
)
def test_orphan_container(
eof_test: EOFTestFiller,
code_section: Section,
first_sub_container: Container,
extra_sub_container: Container,
):
"""Test orphaned containers"""
eof_test(
data=Container(
sections=[
code_section,
first_sub_container,
extra_sub_container,
],
kind=ContainerKind.INITCODE,
),
expect_exception=EOFException.ORPHAN_SUBCONTAINER,
)


@pytest.mark.parametrize(
"code_section,sub_container",
[
pytest.param(
eofcreate_code_section,
returncontract_sub_container,
id="EOFCREATE/RETURNCONTRACT",
),
pytest.param(returncontract_code_section, stop_sub_container, id="RETURNCONTRACT/STOP"),
pytest.param(
returncontract_code_section, return_sub_container, id="RETURNCONTRACT/RETURN"
),
pytest.param(eofcreate_code_section, revert_sub_container, id="EOFCREATE/REVERT"),
pytest.param(
returncontract_code_section, revert_sub_container, id="RETURNCONTRACT/REVERT"
),
],
)
def test_container_combos_valid(
eof_state_test: EOFStateTestFiller,
code_section: Section,
sub_container: Container,
):
"""Test valid subcontainer reference / opcode combos"""
eof_state_test(
deploy_tx=code_section == returncontract_code_section,
data=Container(
sections=[
code_section,
sub_container,
],
),
container_post=Account(storage={slot_code_worked: value_code_worked}),
)


@pytest.mark.parametrize(
"code_section,first_sub_container,error",
[
pytest.param(
eofcreate_code_section,
stop_sub_container,
EOFException.UNDEFINED_EXCEPTION,
id="EOFCREATE/STOP",
),
pytest.param(
eofcreate_code_section,
return_sub_container,
EOFException.UNDEFINED_EXCEPTION,
id="EOFCREATE/RETURN",
),
pytest.param(
returncontract_code_section,
returncontract_sub_container,
EOFException.UNDEFINED_EXCEPTION,
id="RETURNCONTRACT/RETURNCONTRACT",
),
],
)
def test_container_combos_invalid(
eof_test: EOFTestFiller,
code_section: Section,
first_sub_container: Container,
error: EOFException,
):
"""Test invalid subcontainer reference / opcode combos"""
eof_test(
data=Container(
sections=[
code_section,
first_sub_container,
],
),
expect_exception=error,
)


def test_container_both_kinds_same_sub(eof_test: EOFTestFiller):
"""Test subcontainer conflicts (both EOFCREATE and RETURNCONTRACT Reference)"""
eof_test(
data=Container(
sections=[
Section.Code(
code=Op.EOFCREATE[0](0, 0, 0, 0) + Op.JUMPF[1],
),
Section.Code(
code=Op.RETURNCONTRACT[0](0, 0),
),
revert_sub_container,
],
),
expect_exception=EOFException.UNDEFINED_EXCEPTION,
)


def test_container_both_kinds_different_sub(eof_test: EOFTestFiller):
"""Test multiple kinds of subcontainer at the same level"""
eof_test(
data=Container(
sections=[
Section.Code(
code=Op.EOFCREATE[0](0, 0, 0, 0) + Op.JUMPF[1],
),
Section.Code(
code=Op.RETURNCONTRACT[1](0, 0),
),
returncontract_sub_container,
stop_sub_container,
],
),
)

0 comments on commit f514b68

Please sign in to comment.