diff --git a/src/ethereum_test_tools/exceptions/evmone_exceptions.py b/src/ethereum_test_tools/exceptions/evmone_exceptions.py index 23deb5d490..7e1da13411 100644 --- a/src/ethereum_test_tools/exceptions/evmone_exceptions.py +++ b/src/ethereum_test_tools/exceptions/evmone_exceptions.py @@ -74,6 +74,9 @@ class EvmoneExceptionMapper: EOFException.TOPLEVEL_CONTAINER_TRUNCATED, "err: toplevel_container_truncated" ), ExceptionMessage(EOFException.ORPHAN_SUBCONTAINER, "err: unreferenced_subcontainer"), + ExceptionMessage( + EOFException.CONTAINER_SIZE_ABOVE_LIMIT, "err: container_size_above_limit" + ), ) def __init__(self) -> None: diff --git a/src/ethereum_test_tools/exceptions/exceptions.py b/src/ethereum_test_tools/exceptions/exceptions.py index 46e64c60fa..d1d5113879 100644 --- a/src/ethereum_test_tools/exceptions/exceptions.py +++ b/src/ethereum_test_tools/exceptions/exceptions.py @@ -692,6 +692,10 @@ class EOFException(ExceptionBase): """ EOF container has an unreferenced subcontainer. '""" + CONTAINER_SIZE_ABOVE_LIMIT = auto() + """ + EOF container is above size limit + """ """ diff --git a/tests/prague/eip7692_eof_v1/eip3540_eof_v1/test_container_size.py b/tests/prague/eip7692_eof_v1/eip3540_eof_v1/test_container_size.py new file mode 100644 index 0000000000..ea3771c4a1 --- /dev/null +++ b/tests/prague/eip7692_eof_v1/eip3540_eof_v1/test_container_size.py @@ -0,0 +1,114 @@ +""" +EOF validation tests for EIP-3540 container size +""" + +import pytest + +from ethereum_test_tools import EOFTestFiller +from ethereum_test_tools import Opcodes as Op +from ethereum_test_tools.eof.v1 import Container, EOFException, Section +from ethereum_test_tools.eof.v1.constants import MAX_INITCODE_SIZE + +from .. import EOF_FORK_NAME + +REFERENCE_SPEC_GIT_PATH = "EIPS/eip-3540.md" +REFERENCE_SPEC_VERSION = "6b313505c75afa49a4f34de39c609ebebc7be87f" + +pytestmark = pytest.mark.valid_from(EOF_FORK_NAME) + +VALID_CONTAINER = Container(sections=[Section.Code(code=Op.STOP)]) + + +@pytest.mark.parametrize( + "over_limit", + [0, 1, 2, 2**16 - MAX_INITCODE_SIZE], +) +def test_max_size( + eof_test: EOFTestFiller, + over_limit: int, +): + """ + Verify EOF container valid at maximum size, invalid above + """ + # Expand the minimal EOF code by more noop code, reaching the desired target container size. + code = Container( + sections=[ + Section.Code( + code=Op.JUMPDEST * (MAX_INITCODE_SIZE - len(VALID_CONTAINER) + over_limit) + + Op.STOP + ) + ] + ) + assert len(code) == MAX_INITCODE_SIZE + over_limit + eof_test( + data=bytes(code), + expect_exception=None if over_limit == 0 else EOFException.CONTAINER_SIZE_ABOVE_LIMIT, + ) + + +@pytest.mark.parametrize( + "size", + [MAX_INITCODE_SIZE + 1, MAX_INITCODE_SIZE * 2], +) +def test_above_max_size_raw( + eof_test: EOFTestFiller, + size: int, +): + """ + Verify EOF container invalid above maximum size, regardless of header contents + """ + code = Op.INVALID * size + eof_test( + data=bytes(code), + expect_exception=EOFException.CONTAINER_SIZE_ABOVE_LIMIT, + ) + + +@pytest.mark.parametrize( + "code", + [ + pytest.param( + Container(sections=[Section.Code(code=Op.STOP, custom_size=MAX_INITCODE_SIZE)]), + id="1st_code_section", + ), + pytest.param( + Container( + sections=[ + Section.Code(code=Op.STOP), + Section.Code(code=Op.STOP, custom_size=MAX_INITCODE_SIZE), + ] + ), + id="2nd_code_section", + ), + pytest.param( + Container( + sections=[ + Section.Code(code=Op.STOP), + Section.Container(container=Op.STOP, custom_size=MAX_INITCODE_SIZE), + ] + ), + id="1st_container_section", + ), + pytest.param( + Container( + sections=[ + Section.Code(code=Op.STOP), + Section.Container(container=Op.STOP), + Section.Container(container=Op.STOP, custom_size=MAX_INITCODE_SIZE), + ] + ), + id="2nd_container_section", + ), + ], +) +def test_section_after_end_of_container( + eof_test: EOFTestFiller, + code: Container, +): + """ + Verify EOF container is invalid if any of sections declares above container size + """ + eof_test( + data=bytes(code), + expect_exception=EOFException.INVALID_SECTION_BODIES_SIZE, + ) diff --git a/tests/prague/eip7692_eof_v1/eip7480_data_section/test_code_validation.py b/tests/prague/eip7692_eof_v1/eip7480_data_section/test_code_validation.py index 0ed7f755c3..fae49d0676 100644 --- a/tests/prague/eip7692_eof_v1/eip7480_data_section/test_code_validation.py +++ b/tests/prague/eip7692_eof_v1/eip7480_data_section/test_code_validation.py @@ -8,6 +8,7 @@ from ethereum_test_tools import EOFException, EOFTestFiller from ethereum_test_tools.eof.v1 import Container, Section +from ethereum_test_tools.eof.v1.constants import MAX_INITCODE_SIZE from ethereum_test_tools.vm.opcode import Opcodes as Op from .. import EOF_FORK_NAME @@ -17,6 +18,13 @@ pytestmark = pytest.mark.valid_from(EOF_FORK_NAME) +smallest_runtime_subcontainer = Container( + name="Runtime Subcontainer", + sections=[ + Section.Code(code=Op.STOP), + ], +) + VALID: List[Container] = [ Container( name="empty_data_section", @@ -48,10 +56,9 @@ Container( name="max_data_section", sections=[ - Section.Code( - code=Op.ADDRESS + Op.POP + Op.STOP, - ), - Section.Data(data=("1122334455667788" * 8 * 1024)[2:]), + Section.Code(code=Op.STOP), + # Hits the 49152 bytes limit for the entire container + Section.Data(data=b"\x00" * (MAX_INITCODE_SIZE - len(smallest_runtime_subcontainer))), ], ), Container( @@ -81,15 +88,6 @@ Section.Data(data="1122334455667788" * 16), ], ), - Container( - name="DATALOADN_max", - sections=[ - Section.Code( - code=Op.DATALOADN[0xFFFF - 32] + Op.POP + Op.STOP, - ), - Section.Data(data=("1122334455667788" * 8 * 1024)[2:]), - ], - ), ] INVALID: List[Container] = [ @@ -122,6 +120,15 @@ ], validity_error=EOFException.INVALID_DATALOADN_INDEX, ), + Container( + name="data_section_over_container_limit", + sections=[ + Section.Code(code=Op.STOP), + # Over the 49152 bytes limit for the entire container + Section.Data(data=(b"12345678" * 6 * 1024)[len(smallest_runtime_subcontainer) - 1 :]), + ], + validity_error=EOFException.CONTAINER_SIZE_ABOVE_LIMIT, + ), ]