From 255d0e99b029e87bc88c787dfc07559d10af1484 Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath Date: Thu, 5 Sep 2024 12:27:49 +0200 Subject: [PATCH] minor fixes + more eof tests --- src/ethereum/prague/fork.py | 4 +- src/ethereum/prague/utils/message.py | 7 +- src/ethereum/prague/vm/eof/__init__.py | 20 +++- .../prague/vm/eof/instructions_check.py | 24 +++- src/ethereum/prague/vm/eof/utils.py | 52 ++++---- src/ethereum/prague/vm/eof/validation.py | 111 ++++++++++-------- .../prague/vm/instructions/__init__.py | 72 ++++++------ .../prague/vm/instructions/environment.py | 5 +- src/ethereum/prague/vm/instructions/system.py | 34 ++---- tests/helpers/__init__.py | 2 +- tests/prague/test_eof.py | 59 ++++++---- tests/prague/test_evm_tools.py | 4 + 12 files changed, 225 insertions(+), 169 deletions(-) diff --git a/src/ethereum/prague/fork.py b/src/ethereum/prague/fork.py index 4563ac8d84..a1313a4fe6 100644 --- a/src/ethereum/prague/fork.py +++ b/src/ethereum/prague/fork.py @@ -960,9 +960,9 @@ def process_transaction( preaccessed_storage_keys=frozenset(preaccessed_storage_keys), authorizations=authorizations, ) - 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) diff --git a/src/ethereum/prague/utils/message.py b/src/ethereum/prague/utils/message.py index b818e7a984..8a03f6943a 100644 --- a/src/ethereum/prague/utils/message.py +++ b/src/ethereum/prague/utils/message.py @@ -19,7 +19,7 @@ from ..fork_types import Address, Authorization 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 @@ -106,15 +106,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") diff --git a/src/ethereum/prague/vm/eof/__init__.py b/src/ethereum/prague/vm/eof/__init__.py index b11151f488..be0299ad10 100644 --- a/src/ethereum/prague/vm/eof/__init__.py +++ b/src/ethereum/prague/vm/eof/__init__.py @@ -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 @@ -34,6 +34,20 @@ 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: """ @@ -41,6 +55,7 @@ class EofMetadata: EOF container. """ + context: ContainerContext type_size: Uint num_code_sections: Uint code_sizes: List[Uint] @@ -63,8 +78,6 @@ class Eof: version: EofVersion container: Bytes metadata: EofMetadata - is_deploy_container: bool - is_init_container: bool @dataclass @@ -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] diff --git a/src/ethereum/prague/vm/eof/instructions_check.py b/src/ethereum/prague/vm/eof/instructions_check.py index 931138d085..250313f8e4 100644 --- a/src/ethereum/prague/vm/eof/instructions_check.py +++ b/src/ethereum/prague/vm/eof/instructions_check.py @@ -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: @@ -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] @@ -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] = [] @@ -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 @@ -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 @@ -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, {}) @@ -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, {}) diff --git a/src/ethereum/prague/vm/eof/utils.py b/src/ethereum/prague/vm/eof/utils.py index 74a41ace55..ebad03a470 100644 --- a/src/ethereum/prague/vm/eof/utils.py +++ b/src/ethereum/prague/vm/eof/utils.py @@ -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. @@ -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 ------- @@ -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 @@ -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, @@ -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") diff --git a/src/ethereum/prague/vm/eof/validation.py b/src/ethereum/prague/vm/eof/validation.py index b29eb82aba..eac1b03bc7 100644 --- a/src/ethereum/prague/vm/eof/validation.py +++ b/src/ethereum/prague/vm/eof/validation.py @@ -13,7 +13,7 @@ """ from copy import deepcopy -from typing import Tuple +from typing import Set, Tuple from ethereum.base_types import Uint @@ -23,6 +23,7 @@ from . import ( EOF_MAGIC, EOF_MAGIC_LENGTH, + ContainerContext, Eof, EofVersion, OperandStackHeight, @@ -172,6 +173,8 @@ def validate_code_section(validator: Validator) -> None: max=section_inputs, ) op_metadata.stack_height = deepcopy(validator.current_stack_height) + elif op_metadata.stack_height is not None: + validator.current_stack_height = deepcopy(op_metadata.stack_height) # The section has to end in a terminating instruction if index + 1 == len(valid_opcode_positions): @@ -195,6 +198,33 @@ def validate_code_section(validator: Validator) -> None: raise InvalidEof("Invalid stack height") +def get_reached_code_sections( + validator: Validator, + start_index: Uint, + all_reached_sections: Set[Uint], +) -> None: + """ + Get the reached code sections starting from a given index. + + Parameters + ---------- + validator : Validator + The validator for the EOF container. + start_index : Uint + The index to start from. + all_reached_sections : Set[Uint] + The set of all code sections reached until now. + """ + sections_called_from_start_index = validator.reached_code_sections[ + start_index + ] + all_reached_sections.add(start_index) + for i in sections_called_from_start_index: + if i in all_reached_sections: + continue + get_reached_code_sections(validator, i, all_reached_sections) + + def validate_eof_code(validator: Validator) -> None: """ Validate the code section of the EOF container. @@ -215,58 +245,53 @@ def validate_eof_code(validator: Validator) -> None: Ops.RETURNCONTRACT: [], } for code_index, code in enumerate(eof_meta.code_section_contents): + validator.reached_code_sections.append(set()) validator.current_index = Uint(code_index) validator.current_code = code validator.current_pc = Uint(0) validator.sections[validator.current_index] = {} validate_code_section(validator) + all_reached_sections: Set[Uint] = set() + get_reached_code_sections(validator, Uint(0), all_reached_sections) + + for i in range(eof_meta.num_code_sections): + if i not in all_reached_sections: + raise InvalidEof(f"Code section {i} not reachable") + if validator.has_return_contract and ( validator.has_return or validator.has_stop ): raise InvalidEof("Container has both RETURNCONTRACT and STOP/RETURN") if eof_meta.num_container_sections > 0: - eofcreate_references = validator.referenced_subcontainers[ - Ops.EOFCREATE - ] - returncontract_references = validator.referenced_subcontainers[ - Ops.RETURNCONTRACT - ] - for index in range(len(eof_meta.container_section_contents)): - if ( - index in eofcreate_references - and index in returncontract_references - ): + for index in range(eof_meta.num_container_sections): + is_eofcreate_target = ( + index in validator.referenced_subcontainers[Ops.EOFCREATE] + ) + is_returncontract_target = ( + index in validator.referenced_subcontainers[Ops.RETURNCONTRACT] + ) + if is_eofcreate_target and is_returncontract_target: raise InvalidEof( "Container referenced by both EOFCREATE and RETURNCONTRACT" ) - elif ( - index not in eofcreate_references - and index not in returncontract_references - ): + + if is_eofcreate_target: + sub_container_context = ContainerContext.INIT + elif is_returncontract_target: + sub_container_context = ContainerContext.RETURNCONTRACT_TARGET + else: raise InvalidEof("Container never referenced") - sub_validator = validate_eof_container( - eof_meta.container_section_contents[index], False + validate_eof_container( + eof_meta.container_section_contents[index], + sub_container_context, ) - if index in eofcreate_references and ( - sub_validator.has_stop or sub_validator.has_return - ): - raise InvalidEof( - "Container referenced by EOFCREATE has STOP or RETURN" - ) - if ( - index in returncontract_references - and sub_validator.has_return_contract - ): - raise InvalidEof( - "Container referenced by RETURNCONTRACT has RETURNCONTRACT" - ) def validate_eof_container( - container: bytes, is_init_container: bool + container: bytes, context: ContainerContext ) -> Validator: """ Validate the Ethereum Object Format (EOF) container. @@ -275,9 +300,8 @@ def validate_eof_container( ---------- container : bytes The EOF container to validate. - is_init_container : bool - Whether the container is an init container for EOFCREATE/ - create transactions. + context : ContainerContext + Context of the container. Raises ------ @@ -302,16 +326,13 @@ def validate_eof_container( metadata = metadata_from_container( container, validate=True, - is_deploy_container=False, - is_init_container=is_init_container, + context=context, ) eof = Eof( version=EofVersion.EOF1, container=container, metadata=metadata, - is_deploy_container=False, - is_init_container=is_init_container, ) validator = Validator( @@ -323,6 +344,7 @@ def validate_eof_container( has_return_contract=False, has_stop=False, has_return=False, + reached_code_sections=[], referenced_subcontainers={Ops.EOFCREATE: [], Ops.RETURNCONTRACT: []}, current_stack_height=None, ) @@ -331,9 +353,6 @@ def validate_eof_container( validate_eof_code(validator) - if is_init_container and (validator.has_stop or validator.has_return): - raise InvalidEof("Init container has STOP/RETURN") - return validator @@ -354,7 +373,7 @@ def parse_create_tx_call_data(data: bytes) -> Tuple[Eof, bytes]: The data for the create call. """ eof_metadata = metadata_from_container( - data, validate=True, is_deploy_container=False, is_init_container=False + data, validate=True, context=ContainerContext.CREATE_TX_DATA ) container_size = ( @@ -369,8 +388,6 @@ def parse_create_tx_call_data(data: bytes) -> Tuple[Eof, bytes]: version=EofVersion.EOF1, container=data[:container_size], metadata=eof_metadata, - is_deploy_container=False, - is_init_container=True, ) validator = Validator( @@ -382,6 +399,7 @@ def parse_create_tx_call_data(data: bytes) -> Tuple[Eof, bytes]: has_return_contract=False, has_stop=False, has_return=False, + reached_code_sections=[], referenced_subcontainers={}, current_stack_height=None, ) @@ -390,7 +408,4 @@ def parse_create_tx_call_data(data: bytes) -> Tuple[Eof, bytes]: validate_eof_code(validator) - if validator.has_stop or validator.has_return: - raise InvalidEof("Init container has STOP/RETURN") - return eof, data[container_size:] diff --git a/src/ethereum/prague/vm/instructions/__init__.py b/src/ethereum/prague/vm/instructions/__init__.py index a6022f1c14..f2dd30a3f2 100644 --- a/src/ethereum/prague/vm/instructions/__init__.py +++ b/src/ethereum/prague/vm/instructions/__init__.py @@ -594,43 +594,43 @@ class Ops(enum.Enum): Ops.PUSH30: OpcodeStackItemCount(inputs=0, outputs=1), Ops.PUSH31: OpcodeStackItemCount(inputs=0, outputs=1), Ops.PUSH32: OpcodeStackItemCount(inputs=0, outputs=1), - Ops.DUP1: OpcodeStackItemCount(inputs=0, outputs=1), - Ops.DUP2: OpcodeStackItemCount(inputs=0, outputs=1), - Ops.DUP3: OpcodeStackItemCount(inputs=0, outputs=1), - Ops.DUP4: OpcodeStackItemCount(inputs=0, outputs=1), - Ops.DUP5: OpcodeStackItemCount(inputs=0, outputs=1), - Ops.DUP6: OpcodeStackItemCount(inputs=0, outputs=1), - Ops.DUP7: OpcodeStackItemCount(inputs=0, outputs=1), - Ops.DUP8: OpcodeStackItemCount(inputs=0, outputs=1), - Ops.DUP9: OpcodeStackItemCount(inputs=0, outputs=1), - Ops.DUP10: OpcodeStackItemCount(inputs=0, outputs=1), - Ops.DUP11: OpcodeStackItemCount(inputs=0, outputs=1), - Ops.DUP12: OpcodeStackItemCount(inputs=0, outputs=1), - Ops.DUP13: OpcodeStackItemCount(inputs=0, outputs=1), - Ops.DUP14: OpcodeStackItemCount(inputs=0, outputs=1), - Ops.DUP15: OpcodeStackItemCount(inputs=0, outputs=1), - Ops.DUP16: OpcodeStackItemCount(inputs=0, outputs=1), - Ops.SWAP1: OpcodeStackItemCount(inputs=0, outputs=0), - Ops.SWAP2: OpcodeStackItemCount(inputs=0, outputs=0), - Ops.SWAP3: OpcodeStackItemCount(inputs=0, outputs=0), - Ops.SWAP4: OpcodeStackItemCount(inputs=0, outputs=0), - Ops.SWAP5: OpcodeStackItemCount(inputs=0, outputs=0), - Ops.SWAP6: OpcodeStackItemCount(inputs=0, outputs=0), - Ops.SWAP7: OpcodeStackItemCount(inputs=0, outputs=0), - Ops.SWAP8: OpcodeStackItemCount(inputs=0, outputs=0), - Ops.SWAP9: OpcodeStackItemCount(inputs=0, outputs=0), - Ops.SWAP10: OpcodeStackItemCount(inputs=0, outputs=0), - Ops.SWAP11: OpcodeStackItemCount(inputs=0, outputs=0), - Ops.SWAP12: OpcodeStackItemCount(inputs=0, outputs=0), - Ops.SWAP13: OpcodeStackItemCount(inputs=0, outputs=0), - Ops.SWAP14: OpcodeStackItemCount(inputs=0, outputs=0), - Ops.SWAP15: OpcodeStackItemCount(inputs=0, outputs=0), - Ops.SWAP16: OpcodeStackItemCount(inputs=0, outputs=0), + Ops.DUP1: OpcodeStackItemCount(inputs=1, outputs=2), + Ops.DUP2: OpcodeStackItemCount(inputs=2, outputs=3), + Ops.DUP3: OpcodeStackItemCount(inputs=3, outputs=4), + Ops.DUP4: OpcodeStackItemCount(inputs=4, outputs=5), + Ops.DUP5: OpcodeStackItemCount(inputs=5, outputs=6), + Ops.DUP6: OpcodeStackItemCount(inputs=6, outputs=7), + Ops.DUP7: OpcodeStackItemCount(inputs=7, outputs=8), + Ops.DUP8: OpcodeStackItemCount(inputs=8, outputs=9), + Ops.DUP9: OpcodeStackItemCount(inputs=9, outputs=10), + Ops.DUP10: OpcodeStackItemCount(inputs=10, outputs=11), + Ops.DUP11: OpcodeStackItemCount(inputs=11, outputs=12), + Ops.DUP12: OpcodeStackItemCount(inputs=12, outputs=13), + Ops.DUP13: OpcodeStackItemCount(inputs=13, outputs=14), + Ops.DUP14: OpcodeStackItemCount(inputs=14, outputs=15), + Ops.DUP15: OpcodeStackItemCount(inputs=15, outputs=16), + Ops.DUP16: OpcodeStackItemCount(inputs=16, outputs=17), + Ops.SWAP1: OpcodeStackItemCount(inputs=2, outputs=2), + Ops.SWAP2: OpcodeStackItemCount(inputs=3, outputs=3), + Ops.SWAP3: OpcodeStackItemCount(inputs=4, outputs=4), + Ops.SWAP4: OpcodeStackItemCount(inputs=5, outputs=5), + Ops.SWAP5: OpcodeStackItemCount(inputs=6, outputs=6), + Ops.SWAP6: OpcodeStackItemCount(inputs=7, outputs=7), + Ops.SWAP7: OpcodeStackItemCount(inputs=8, outputs=8), + Ops.SWAP8: OpcodeStackItemCount(inputs=9, outputs=9), + Ops.SWAP9: OpcodeStackItemCount(inputs=10, outputs=10), + Ops.SWAP10: OpcodeStackItemCount(inputs=11, outputs=11), + Ops.SWAP11: OpcodeStackItemCount(inputs=12, outputs=12), + Ops.SWAP12: OpcodeStackItemCount(inputs=13, outputs=13), + Ops.SWAP13: OpcodeStackItemCount(inputs=14, outputs=14), + Ops.SWAP14: OpcodeStackItemCount(inputs=15, outputs=15), + Ops.SWAP15: OpcodeStackItemCount(inputs=16, outputs=16), + Ops.SWAP16: OpcodeStackItemCount(inputs=17, outputs=17), Ops.LOG0: OpcodeStackItemCount(inputs=2, outputs=0), - Ops.LOG1: OpcodeStackItemCount(inputs=2, outputs=0), - Ops.LOG2: OpcodeStackItemCount(inputs=2, outputs=0), - Ops.LOG3: OpcodeStackItemCount(inputs=2, outputs=0), - Ops.LOG4: OpcodeStackItemCount(inputs=2, outputs=0), + Ops.LOG1: OpcodeStackItemCount(inputs=3, outputs=0), + Ops.LOG2: OpcodeStackItemCount(inputs=4, outputs=0), + Ops.LOG3: OpcodeStackItemCount(inputs=5, outputs=0), + Ops.LOG4: OpcodeStackItemCount(inputs=6, outputs=0), Ops.DATALOAD: OpcodeStackItemCount(inputs=1, outputs=1), Ops.DATALOADN: OpcodeStackItemCount(inputs=0, outputs=1), Ops.DATASIZE: OpcodeStackItemCount(inputs=0, outputs=1), diff --git a/src/ethereum/prague/vm/instructions/environment.py b/src/ethereum/prague/vm/instructions/environment.py index 89c058a9ca..2b4f81bf8d 100644 --- a/src/ethereum/prague/vm/instructions/environment.py +++ b/src/ethereum/prague/vm/instructions/environment.py @@ -411,9 +411,8 @@ def extcodecopy(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by eof_version = get_eof_version(code) if eof_version == EofVersion.EOF1: - value = EOF_MAGIC - else: - value = buffer_read(code, code_start_index, size) + code = EOF_MAGIC + value = buffer_read(code, code_start_index, size) memory_write(evm.memory, memory_start_index, value) # PROGRAM COUNTER diff --git a/src/ethereum/prague/vm/instructions/system.py b/src/ethereum/prague/vm/instructions/system.py index 542611e7bd..f76bb61aed 100644 --- a/src/ethereum/prague/vm/instructions/system.py +++ b/src/ethereum/prague/vm/instructions/system.py @@ -297,7 +297,7 @@ def generic_call( Perform the core logic of the `CALL*` family of opcodes. """ from ...vm.interpreter import STACK_DEPTH_LIMIT, process_message - from ..eof import Eof, EofVersion, get_eof_version + from ..eof import ContainerContext, Eof, EofVersion, get_eof_version evm.return_data = b"" @@ -318,20 +318,15 @@ def generic_call( if eof_version == Eof.LEGACY: is_init_container = None else: - is_init_container = False - is_deploy_container = False metadata = metadata_from_container( code, validate=False, - is_init_container=is_init_container, - is_deploy_container=is_deploy_container, + context=ContainerContext.RUNTIME, ) eof = Eof( version=eof_version, container=code, metadata=metadata, - is_init_container=is_init_container, - is_deploy_container=is_deploy_container, ) child_message = Message( @@ -840,7 +835,7 @@ def generic_eof_call( If a write operation is attempted in a static context. """ from ...vm.interpreter import STACK_DEPTH_LIMIT, process_message - from ..eof import Eof, EofVersion, get_eof_version + from ..eof import ContainerContext, Eof, EofVersion, get_eof_version if evm.gas_left < EOF_CALL_MIN_RETAINED_GAS: push(evm.stack, U256(1)) @@ -877,20 +872,15 @@ def generic_eof_call( if eof_version == EofVersion.LEGACY: eof = None else: - is_init_container = False - is_deploy_container = False metadata = metadata_from_container( container, validate=False, - is_init_container=is_init_container, - is_deploy_container=is_deploy_container, + context=ContainerContext.RUNTIME, ) eof = Eof( version=eof_version, container=container, metadata=metadata, - is_init_container=is_init_container, - is_deploy_container=is_deploy_container, ) child_message = Message( @@ -1031,7 +1021,7 @@ def ext_delegatecall(evm: Evm) -> None: code_address=target_address, value=U256(0), should_transfer_value=False, - is_static=False, + is_static=evm.message.is_static, allow_legacy_code=False, ) @@ -1099,7 +1089,7 @@ def eof_create(evm: Evm) -> None: The current EVM frame. """ from ...vm.interpreter import STACK_DEPTH_LIMIT, process_create_message - from ..eof import Eof, get_eof_version + from ..eof import ContainerContext, Eof, get_eof_version assert evm.eof is not None @@ -1157,20 +1147,15 @@ def eof_create(evm: Evm) -> None: increment_nonce(evm.env.state, evm.message.current_target) - is_init_container = True - is_deploy_container = False metadata = metadata_from_container( init_container, validate=True, - is_deploy_container=is_deploy_container, - is_init_container=is_init_container, + context=ContainerContext.INIT, ) eof = Eof( version=get_eof_version(init_container), container=init_container, metadata=metadata, - is_init_container=True, - is_deploy_container=False, ) child_message = Message( @@ -1216,6 +1201,8 @@ def return_contract(evm: Evm) -> None: evm : The current EVM frame. """ + from ..eof import ContainerContext + assert evm.eof is not None # STACK @@ -1239,8 +1226,7 @@ def return_contract(evm: Evm) -> None: deploy_container_metadata = metadata_from_container( deploy_container, validate=True, - is_deploy_container=True, - is_init_container=False, + context=ContainerContext.RETURNCONTRACT_TARGET, ) aux_data = memory_read_bytes(evm.memory, aux_data_offset, aux_data_size) deploy_container_metadata.data_section_contents += aux_data diff --git a/tests/helpers/__init__.py b/tests/helpers/__init__.py index 51d1c52043..b858784439 100644 --- a/tests/helpers/__init__.py +++ b/tests/helpers/__init__.py @@ -17,7 +17,7 @@ }, "latest_fork_tests": { "url": "https://github.com/gurukamath/latest_fork_tests.git", - "commit_hash": "0a8ce60", + "commit_hash": "1a9b58e", "fixture_path": "tests/fixtures/latest_fork_tests", }, } diff --git a/tests/prague/test_eof.py b/tests/prague/test_eof.py index 38d4f3ce01..795cb3d593 100644 --- a/tests/prague/test_eof.py +++ b/tests/prague/test_eof.py @@ -1,35 +1,45 @@ import json import os from glob import glob -from typing import Dict, Generator +from typing import Dict, Generator, Tuple import pytest +from ethereum.prague.vm.eof import ContainerContext from ethereum.prague.vm.eof.validation import validate_eof_container from ethereum.prague.vm.exceptions import InvalidEof from ethereum.utils.hexadecimal import hex_to_bytes -TEST_DIR = "tests/fixtures/latest_fork_tests/eof_tests/prague/eip7692_eof_v1" +TEST_DIRS = ( + "tests/fixtures/latest_fork_tests/eof_tests/prague/eip7692_eof_v1", + "tests/fixtures/ethereum_tests/EOFTests/", + "tests/fixtures/latest_fork_tests/evmone_tests/eof_tests", +) -def fetch_eof_tests(test_dir: str) -> Generator: - all_jsons = [ - y - for x in os.walk(test_dir) - for y in glob(os.path.join(x[0], "*.json")) - ] +def fetch_eof_tests(test_dirs: Tuple[str, ...]) -> Generator: + for test_dir in test_dirs: + all_jsons = [ + y + for x in os.walk(test_dir) + for y in glob(os.path.join(x[0], "*.json")) + ] - for full_path in all_jsons: - # Read the json file and yield the test cases - with open(full_path, "r") as file: - data = json.load(file) - for test in data.keys(): - for key in data[test]["vectors"].keys(): - yield { - "test_file": full_path, - "test_name": test, - "test_key": key, - } + for full_path in all_jsons: + # Read the json file and yield the test cases + with open(full_path, "r") as file: + data = json.load(file) + for test in data.keys(): + try: + keys = data[test]["vectors"].keys() + except AttributeError: + continue + for key in keys: + yield { + "test_file": full_path, + "test_name": test, + "test_key": key, + } # Test case Identifier @@ -47,7 +57,7 @@ def idfn(test_case: Dict) -> str: # Run the tests @pytest.mark.parametrize( "test_case", - fetch_eof_tests(TEST_DIR), + fetch_eof_tests(TEST_DIRS), ids=idfn, ) def test_eof(test_case: Dict) -> None: @@ -61,6 +71,11 @@ def test_eof(test_case: Dict) -> None: # Extract the test data code = hex_to_bytes(test_vector["code"]) + if test_vector.get("containerKind") == "INITCODE": + context = ContainerContext.INIT + else: + context = ContainerContext.RUNTIME + prague_validation = test_vector["results"]["Prague"] if "exception" in prague_validation and prague_validation["result"]: @@ -68,6 +83,6 @@ def test_eof(test_case: Dict) -> None: if "exception" in prague_validation: with pytest.raises(InvalidEof): - validate_eof_container(code, False) + validate_eof_container(code, context) else: - validate_eof_container(code, False) + validate_eof_container(code, context) diff --git a/tests/prague/test_evm_tools.py b/tests/prague/test_evm_tools.py index 9fb3a7be59..ad6a9fccd5 100644 --- a/tests/prague/test_evm_tools.py +++ b/tests/prague/test_evm_tools.py @@ -48,6 +48,10 @@ "tests/prague/eip7702_set_code_tx/test_set_code_txs.py::test_invalid_tx_invalid_auth_signature[fork_Prague-state_test-v_2**256-1,r_1,s_1]", "tests/prague/eip7702_set_code_tx/test_set_code_txs.py::test_invalid_tx_invalid_auth_signature[fork_Prague-state_test-v_0,r_1,s_2**256-1]", "tests/fixtures/latest_fork_tests/state_tests/prague/eip7692_eof_v1", + "tests/fixtures/ethereum_tests/EIPTests/StateTests/stEOF", + # # TODO: Run evmone tests after they have been fixed + # # There are currently some malformed fixtures in the evmone tests + # "tests/fixtures/latest_fork_tests/evmone_tests/state_tests", )