Skip to content

Commit

Permalink
minor fixes + more eof tests
Browse files Browse the repository at this point in the history
  • Loading branch information
gurukamath committed Sep 6, 2024
1 parent 0157e72 commit b234ede
Show file tree
Hide file tree
Showing 12 changed files with 226 additions and 170 deletions.
4 changes: 2 additions & 2 deletions src/ethereum/prague/fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -926,9 +926,9 @@ def process_transaction(
preaccessed_addresses=frozenset(preaccessed_addresses),
preaccessed_storage_keys=frozenset(preaccessed_storage_keys),
)
except InvalidEof:
except InvalidEof as error:
output = MessageCallOutput(
gas, U256(0), tuple(), set(), set(), InvalidEof(), b""
gas, U256(0), tuple(), set(), set(), error, b""
)
else:
output = process_message_call(message, env)
Expand Down
7 changes: 2 additions & 5 deletions src/ethereum/prague/utils/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from ..fork_types import Address
from ..state import get_account
from ..vm import Environment, Message
from ..vm.eof import Eof, EofVersion, get_eof_version
from ..vm.eof import ContainerContext, Eof, EofVersion, get_eof_version
from ..vm.eof.utils import metadata_from_container
from ..vm.eof.validation import parse_create_tx_call_data
from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS
Expand Down Expand Up @@ -103,15 +103,12 @@ def prepare_message(
metadata = metadata_from_container(
code,
validate=False,
is_deploy_container=False,
is_init_container=False,
context=ContainerContext.RUNTIME,
)
eof = Eof(
version=get_eof_version(code),
container=code,
metadata=metadata,
is_init_container=False,
is_deploy_container=False,
)
else:
raise AssertionError("Target must be address or empty bytes")
Expand Down
20 changes: 17 additions & 3 deletions src/ethereum/prague/vm/eof/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

import enum
from dataclasses import dataclass
from typing import Dict, List, Optional
from typing import Dict, List, Optional, Set

from ethereum.base_types import Bytes, Uint

Expand All @@ -34,13 +34,28 @@ class EofVersion(enum.Enum):
EOF1 = 1


class ContainerContext(enum.Enum):
"""
The context of the container. Create transaction
data / init / account code / sub-container.
A sub-container can either be an EOFCREATE target (init)
or a RETURNCONTRACT target.
"""

CREATE_TX_DATA = 0
INIT = 1
RUNTIME = 2
RETURNCONTRACT_TARGET = 3


@dataclass
class EofMetadata:
"""
Dataclass to hold the metadata information of the
EOF container.
"""

context: ContainerContext
type_size: Uint
num_code_sections: Uint
code_sizes: List[Uint]
Expand All @@ -63,8 +78,6 @@ class Eof:
version: EofVersion
container: Bytes
metadata: EofMetadata
is_deploy_container: bool
is_init_container: bool


@dataclass
Expand Down Expand Up @@ -122,6 +135,7 @@ class Validator:
has_return_contract: bool
has_stop: bool
has_return: bool
reached_code_sections: List[Set[Uint]]
referenced_subcontainers: Dict[Ops, List[Uint]]
current_stack_height: Optional[OperandStackHeight]

Expand Down
24 changes: 22 additions & 2 deletions src/ethereum/prague/vm/eof/instructions_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from ..exceptions import InvalidEof
from ..instructions import EOF1_TERMINATING_INSTRUCTIONS, Ops, map_int_to_op
from . import EofVersion, InstructionMetadata, Validator
from . import ContainerContext, EofVersion, InstructionMetadata, Validator


def validate_push(validator: Validator) -> None:
Expand Down Expand Up @@ -72,6 +72,8 @@ def validate_callf(validator: Validator) -> None:
counter += 2
if target_index >= eof_meta.num_code_sections:
raise InvalidEof("Invalid target code section index")
reached_sections = validator.reached_code_sections[validator.current_index]
reached_sections.add(target_index)

target_type = eof_meta.type_section_contents[target_index]
target_outputs = target_type[1]
Expand Down Expand Up @@ -249,6 +251,8 @@ def validate_jumpf(validator: Validator) -> None:

if target_outputs != 0x80 and target_outputs > current_outputs:
raise InvalidEof("Invalid stack height")
reached_sections = validator.reached_code_sections[validator.current_index]
reached_sections.add(target_index)

# Successor instruction positions
relative_offsets: List[int] = []
Expand Down Expand Up @@ -284,7 +288,7 @@ def validate_dataloadn(validator: Validator) -> None:
if len(code) < counter + 2:
raise InvalidEof("DATALOADN offset missing")
offset = Uint.from_be_bytes(code[position + 1 : position + 3])
if offset >= eof_meta.data_size:
if offset + 32 > eof_meta.data_size:
raise InvalidEof("Invalid DATALOADN offset")
counter += 2

Expand Down Expand Up @@ -452,6 +456,15 @@ def validate_returncontract(validator: Validator) -> None:
validator : `Validator`
The current validator instance.
"""
context = validator.eof.metadata.context
if context in (
ContainerContext.RETURNCONTRACT_TARGET,
ContainerContext.RUNTIME,
):
raise InvalidEof(
"RETURNCONTRACT instruction in RUNTIME "
"container/RETURNCONTRACT target"
)
code = validator.current_code
position = Uint(validator.current_pc)
counter = validator.current_pc + 1
Expand Down Expand Up @@ -493,6 +506,10 @@ def validate_stop(validator: Validator) -> None:
validator : `Validator`
The current validator instance.
"""
context = validator.eof.metadata.context
if context in (ContainerContext.INIT, ContainerContext.CREATE_TX_DATA):
raise InvalidEof("STOP instruction in EOFCREATE target/ initcode")

position = Uint(validator.current_pc)
counter = validator.current_pc + 1
current_metadata = validator.sections.get(validator.current_index, {})
Expand Down Expand Up @@ -522,6 +539,9 @@ def validate_return(validator: Validator) -> None:
validator : `Validator`
The current validator instance.
"""
context = validator.eof.metadata.context
if context in (ContainerContext.INIT, ContainerContext.CREATE_TX_DATA):
raise InvalidEof("RETURN instruction in EOFCREATE target/ initcode")
position = Uint(validator.current_pc)
counter = validator.current_pc + 1
current_metadata = validator.sections.get(validator.current_index, {})
Expand Down
52 changes: 29 additions & 23 deletions src/ethereum/prague/vm/eof/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@
from ethereum.base_types import Uint

from ..exceptions import InvalidEof
from . import EOF_MAGIC, EofMetadata
from . import EOF_MAGIC, ContainerContext, EofMetadata


def metadata_from_container(
container: bytes,
validate: bool,
is_deploy_container: bool,
is_init_container: bool,
context: ContainerContext,
) -> EofMetadata:
"""
Validate the header of the EOF container.
Expand All @@ -24,12 +23,8 @@ def metadata_from_container(
Whether to validate the EOF container. If the container is simply read
from an existing account, it is assumed to be validated. However, if
the container is being created, it should be validated first.
is_deploy_container : bool
Whether the container is a deploy container for EOFCREATE/ create
transactions.
is_init_container : bool
Whether the container is an init container for EOFCREATE/
create transactions.
context: ContainerContext
The context of the container.
Returns
-------
Expand Down Expand Up @@ -127,6 +122,9 @@ def metadata_from_container(
raise InvalidEof("Invalid container size")
container_sizes.append(container_size)

if validate and len(container) < counter + 1:
raise InvalidEof("Kind data not specified in header")

# Get 1 byte kind_data
kind_data = container[counter]
counter += 1
Expand Down Expand Up @@ -171,28 +169,36 @@ def metadata_from_container(
)
counter += container_size

if (
validate
and not is_deploy_container
and len(container) < counter + data_size
):
raise InvalidEof("Data section size does not match header")
if validate:
if (
context == ContainerContext.INIT
and len(container) != counter + data_size
):
raise InvalidEof("Invalid init container data size")

if (
validate
and is_init_container
and len(container) != counter + data_size
):
raise InvalidEof("invalid init container data size")
elif (
context
in (
ContainerContext.RUNTIME,
ContainerContext.CREATE_TX_DATA,
)
and len(container) < counter + data_size
):
raise InvalidEof("Data section size does not match header")

data_section_contents = container[counter : counter + data_size]
counter += data_size

# Check for stray bytes after the data section
if validate and len(container) > counter:
if (
validate
and len(container) > counter
and context != ContainerContext.CREATE_TX_DATA
):
raise InvalidEof("Stray bytes found after data section")

return EofMetadata(
context=context,
type_size=type_size,
num_code_sections=num_code_sections,
code_sizes=code_sizes,
Expand Down Expand Up @@ -240,7 +246,7 @@ def container_from_metadata(eof_metadata: EofMetadata) -> bytes:
# Add the kind container
if eof_metadata.num_container_sections > 0:
container += b"\x03"
container += eof_metadata.num_container_sections.to_be_bytes()
container += eof_metadata.num_container_sections.to_bytes(2, "big")
for container_size in eof_metadata.container_sizes:
container += container_size.to_bytes(2, "big")

Expand Down
Loading

0 comments on commit b234ede

Please sign in to comment.