diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 8ce0351465f..48bf1e45fad 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -53,7 +53,8 @@ Users can select any of the artifacts depending on their testing needs for their ### 📋 Misc -- Remove Python 3.10 support ([#1808](https://github.com/ethereum/execution-spec-tests/pull/1808)). +- 🔀 Remove Python 3.10 support ([#1808](https://github.com/ethereum/execution-spec-tests/pull/1808)). +- 🔀 Modernize codebase with Python 3.11 language features ([#1812](https://github.com/ethereum/execution-spec-tests/pull/1812)). - ✨ Add changelog formatting validation to CI to ensure consistent punctuation in bullet points [#1691](https://github.com/ethereum/execution-spec-tests/pull/1691). - ✨ Added the [EIP checklist template](https://eest.ethereum.org/main/writing_tests/checklist_templates/eip_testing_checklist_template/) that serves as a reference to achieve better coverage when implementing tests for new EIPs ([#1327](https://github.com/ethereum/execution-spec-tests/pull/1327)). - ✨ Added [Post-Mortems of Missed Test Scenarios](https://eest.ethereum.org/main/writing_tests/post_mortems/) to the documentation that serves as a reference list of all cases that were missed during the test implementation phase of a new EIP, and includes the steps taken in order to prevent similar test cases to be missed in the future ([#1327](https://github.com/ethereum/execution-spec-tests/pull/1327)). diff --git a/src/cli/order_fixtures.py b/src/cli/order_fixtures.py index caa08dce936..7b90281452e 100644 --- a/src/cli/order_fixtures.py +++ b/src/cli/order_fixtures.py @@ -17,12 +17,12 @@ import json from pathlib import Path -from typing import Any, Dict, List, Union, cast +from typing import Any, Dict, List, cast import click -def recursive_sort(item: Union[Dict[str, Any], List[Any]]) -> Union[Dict[str, Any], List[Any]]: +def recursive_sort(item: Dict[str, Any] | List[Any]) -> Dict[str, Any] | List[Any]: """ Recursively sorts an item. diff --git a/src/ethereum_clis/transition_tool.py b/src/ethereum_clis/transition_tool.py index 27b0471baaf..9340d2f1188 100644 --- a/src/ethereum_clis/transition_tool.py +++ b/src/ethereum_clis/transition_tool.py @@ -10,7 +10,7 @@ from abc import abstractmethod from dataclasses import dataclass from pathlib import Path -from typing import Any, Dict, List, Mapping, Optional, Type +from typing import Any, Dict, List, LiteralString, Mapping, Optional, Type from urllib.parse import urlencode from requests import Response @@ -20,6 +20,7 @@ from ethereum_test_base_types import BlobSchedule from ethereum_test_exceptions import ExceptionMapper from ethereum_test_forks import Fork +from ethereum_test_forks.helpers import get_development_forks, get_forks from ethereum_test_types import Alloc, Environment, Transaction from .ethereum_cli import EthereumCLI @@ -38,6 +39,12 @@ SLOW_REQUEST_TIMEOUT = 180 +def get_valid_transition_tool_names() -> set[str]: + """Get all valid transition tool names from deployed and development forks.""" + all_available_forks = get_forks() + get_development_forks() + return {fork.transition_tool_name() for fork in all_available_forks} + + class TransitionTool(EthereumCLI): """ Transition tool abstract base class which should be inherited by all transition tool @@ -424,6 +431,49 @@ def _evaluate_stream( return output + def safe_t8n_args( + self, fork_name: str, chain_id: int, reward: int, temp_dir=None + ) -> List[str]: + """Safely construct t8n arguments with validated inputs.""" + # Validate fork name against actual transition tool names from all available forks + valid_forks = get_valid_transition_tool_names() + if fork_name not in valid_forks: + raise ValueError(f"Invalid fork name: {fork_name}") + + # Validate chain ID (should be positive integer) + if not isinstance(chain_id, int) or chain_id <= 0: + raise ValueError(f"Invalid chain ID: {chain_id}") + + # Validate reward (should be non-negative integer) + if not isinstance(reward, int) or reward < 0: + raise ValueError(f"Invalid reward: {reward}") + + # Use literal strings for command flags + input_alloc: LiteralString = "--input.alloc=stdin" + input_txs: LiteralString = "--input.txs=stdin" + input_env: LiteralString = "--input.env=stdin" + output_result: LiteralString = "--output.result=stdout" + output_alloc: LiteralString = "--output.alloc=stdout" + output_body: LiteralString = "--output.body=stdout" + trace_flag: LiteralString = "--trace" + + args = [ + input_alloc, + input_txs, + input_env, + output_result, + output_alloc, + output_body, + f"--state.fork={fork_name}", + f"--state.chainid={chain_id}", + f"--state.reward={reward}", + ] + + if self.trace and temp_dir: + args.extend([trace_flag, f"--output.basedir={temp_dir.name}"]) + + return args + def construct_args_stream( self, t8n_data: TransitionToolData, temp_dir: tempfile.TemporaryDirectory ) -> List[str]: @@ -432,22 +482,10 @@ def construct_args_stream( if self.subcommand: command.append(self.subcommand) - args = command + [ - "--input.alloc=stdin", - "--input.txs=stdin", - "--input.env=stdin", - "--output.result=stdout", - "--output.alloc=stdout", - "--output.body=stdout", - f"--state.fork={t8n_data.fork_name}", - f"--state.chainid={t8n_data.chain_id}", - f"--state.reward={t8n_data.reward}", - ] - - if self.trace: - args.append("--trace") - args.append(f"--output.basedir={temp_dir.name}") - return args + safe_args = self.safe_t8n_args( + t8n_data.fork_name, t8n_data.chain_id, t8n_data.reward, temp_dir + ) + return command + safe_args def dump_debug_stream( self, diff --git a/src/ethereum_test_base_types/base_types.py b/src/ethereum_test_base_types/base_types.py index 696bdfbff27..a35a4cd3978 100644 --- a/src/ethereum_test_base_types/base_types.py +++ b/src/ethereum_test_base_types/base_types.py @@ -21,8 +21,6 @@ to_number, ) -N = TypeVar("N", bound="Number") - class ToStringSchema: """ @@ -44,7 +42,7 @@ def __get_pydantic_core_schema__( class Number(int, ToStringSchema): """Class that helps represent numbers in tests.""" - def __new__(cls, input_number: NumberConvertible | N): + def __new__(cls, input_number: NumberConvertible | Self): """Create a new Number object.""" return super(Number, cls).__new__(cls, to_number(input_number)) @@ -57,7 +55,7 @@ def hex(self) -> str: return hex(self) @classmethod - def or_none(cls: Type[N], input_number: N | NumberConvertible | None) -> N | None: + def or_none(cls: Type[Self], input_number: Self | NumberConvertible | None) -> Self | None: """Convert the input to a Number while accepting None.""" if input_number is None: return input_number @@ -67,7 +65,7 @@ def or_none(cls: Type[N], input_number: N | NumberConvertible | None) -> N | Non class Wei(Number): """Class that helps represent wei that can be parsed from strings.""" - def __new__(cls, input_number: NumberConvertible | N): + def __new__(cls, input_number: NumberConvertible | Self): """Create a new Number object.""" if isinstance(input_number, str): words = input_number.split() @@ -209,9 +207,6 @@ def __get_pydantic_core_schema__( ) -S = TypeVar("S", bound="FixedSizeHexNumber") - - class FixedSizeHexNumber(int, ToStringSchema): """ A base class that helps represent an integer as a fixed byte-length @@ -233,7 +228,7 @@ class Sized(cls): # type: ignore return Sized - def __new__(cls, input_number: NumberConvertible | N): + def __new__(cls, input_number: NumberConvertible | Self): """Create a new Number object.""" i = to_number(input_number) if i > cls.max_value: @@ -276,9 +271,6 @@ class HashInt(FixedSizeHexNumber[32]): # type: ignore pass -T = TypeVar("T", bound="FixedSizeBytes") - - class FixedSizeBytes(Bytes): """Class that helps represent bytes of fixed length in tests.""" @@ -296,7 +288,7 @@ class Sized(cls): # type: ignore def __new__( cls, - input_bytes: FixedSizeBytesConvertible | T, + input_bytes: FixedSizeBytesConvertible | Self, *, left_padding: bool = False, right_padding: bool = False, @@ -319,7 +311,9 @@ def __hash__(self) -> int: return super(FixedSizeBytes, self).__hash__() @classmethod - def or_none(cls: Type[T], input_bytes: T | FixedSizeBytesConvertible | None) -> T | None: + def or_none( + cls: Type[Self], input_bytes: Self | FixedSizeBytesConvertible | None + ) -> Self | None: """Convert the input to a Fixed Size Bytes while accepting None.""" if input_bytes is None: return input_bytes diff --git a/src/ethereum_test_base_types/pydantic.py b/src/ethereum_test_base_types/pydantic.py index a28d89f0066..6471fe07fa3 100644 --- a/src/ethereum_test_base_types/pydantic.py +++ b/src/ethereum_test_base_types/pydantic.py @@ -4,11 +4,10 @@ from pydantic import BaseModel, ConfigDict, RootModel from pydantic.alias_generators import to_camel +from typing_extensions import Self from .mixins import ModelCustomizationsMixin -Model = TypeVar("Model", bound=BaseModel) - RootModelRootType = TypeVar("RootModelRootType") @@ -27,7 +26,7 @@ class EthereumTestRootModel(RootModel[RootModelRootType], ModelCustomizationsMix class CopyValidateModel(EthereumTestBaseModel): """Model that supports copying with validation.""" - def copy(self: Model, **kwargs) -> Model: + def copy(self: Self, **kwargs) -> Self: """Create a copy of the model with the updated fields that are validated.""" return self.__class__(**(self.model_dump(exclude_unset=True) | kwargs)) diff --git a/src/ethereum_test_fixtures/collector.py b/src/ethereum_test_fixtures/collector.py index d93cc1fd7bb..0a832aab692 100644 --- a/src/ethereum_test_fixtures/collector.py +++ b/src/ethereum_test_fixtures/collector.py @@ -18,7 +18,7 @@ from .file import Fixtures -@dataclass(kw_only=True) +@dataclass(kw_only=True, slots=True) class TestInfo: """Contains test information from the current node.""" @@ -34,9 +34,9 @@ class TestInfo: def strip_test_name(cls, name: str) -> str: """Remove test prefix from a python test case name.""" if name.startswith(cls.test_prefix): - return name[len(cls.test_prefix) :] + return name.removeprefix(cls.test_prefix) if name.endswith(cls.filler_suffix): - return name[: -len(cls.filler_suffix)] + return name.removesuffix(cls.filler_suffix) return name def get_name_and_parameters(self) -> Tuple[str, str]: diff --git a/src/ethereum_test_rpc/rpc.py b/src/ethereum_test_rpc/rpc.py index 9ebdf5ea972..4d58859af0e 100644 --- a/src/ethereum_test_rpc/rpc.py +++ b/src/ethereum_test_rpc/rpc.py @@ -3,7 +3,7 @@ import time from itertools import count from pprint import pprint -from typing import Any, ClassVar, Dict, List, Literal, Union +from typing import Any, ClassVar, Dict, List, Literal import requests from jwt import encode @@ -23,7 +23,7 @@ TransactionByHashResponse, ) -BlockNumberType = Union[int, Literal["latest", "earliest", "pending"]] +BlockNumberType = int | Literal["latest", "earliest", "pending"] class SendTransactionExceptionError(Exception): @@ -71,7 +71,7 @@ def __init_subclass__(cls) -> None: """Set namespace of the RPC class to the lowercase of the class name.""" namespace = cls.__name__ if namespace.endswith("RPC"): - namespace = namespace[:-3] + namespace = namespace.removesuffix("RPC") cls.namespace = namespace.lower() def post_request(self, method: str, *params: Any, extra_headers: Dict | None = None) -> Any: @@ -111,7 +111,7 @@ class EthRPC(BaseRPC): transaction_wait_timeout: int = 60 - BlockNumberType = Union[int, Literal["latest", "earliest", "pending"]] + BlockNumberType = int | Literal["latest", "earliest", "pending"] def __init__( self, diff --git a/src/ethereum_test_rpc/types.py b/src/ethereum_test_rpc/types.py index 23e7ba0f3ac..4b42c85c41c 100644 --- a/src/ethereum_test_rpc/types.py +++ b/src/ethereum_test_rpc/types.py @@ -2,7 +2,7 @@ from enum import Enum from hashlib import sha256 -from typing import Annotated, Any, List, Union +from typing import Annotated, Any, List from pydantic import AliasChoices, Field, model_validator @@ -97,7 +97,7 @@ class PayloadStatusEnum(str, Enum): class BlockTransactionExceptionWithMessage( - ExceptionWithMessage[Union[BlockException, TransactionException]] # type: ignore + ExceptionWithMessage[BlockException | TransactionException] # type: ignore ): """Exception returned from the execution client with a message.""" diff --git a/src/ethereum_test_specs/base.py b/src/ethereum_test_specs/base.py index c5613dc22ae..ccd6739e1a2 100644 --- a/src/ethereum_test_specs/base.py +++ b/src/ethereum_test_specs/base.py @@ -5,10 +5,11 @@ from functools import reduce from os import path from pathlib import Path -from typing import Callable, ClassVar, Dict, Generator, List, Sequence, Type, TypeVar +from typing import Callable, ClassVar, Dict, Generator, List, Sequence, Type import pytest from pydantic import BaseModel, Field, PrivateAttr +from typing_extensions import Self from ethereum_clis import Result, TransitionTool from ethereum_test_base_types import to_hex @@ -48,9 +49,6 @@ def verify_result(result: Result, env: Environment): assert result.withdrawals_root == to_hex(Withdrawal.list_root(env.withdrawals)) -T = TypeVar("T", bound="BaseTest") - - class BaseTest(BaseModel): """Represents a base Ethereum test which must return a single test fixture.""" @@ -91,11 +89,11 @@ def __pydantic_init_subclass__(cls, **kwargs): @classmethod def from_test( - cls: Type[T], + cls: Type[Self], *, base_test: "BaseTest", **kwargs, - ) -> T: + ) -> Self: """Create a test in a different format from a base test.""" new_instance = cls( tag=base_test.tag, diff --git a/src/ethereum_test_specs/helpers.py b/src/ethereum_test_specs/helpers.py index 35440649a49..1f877f21d30 100644 --- a/src/ethereum_test_specs/helpers.py +++ b/src/ethereum_test_specs/helpers.py @@ -1,7 +1,7 @@ """Helper functions.""" from dataclasses import dataclass -from enum import Enum +from enum import StrEnum from typing import Any, Dict, List from ethereum_clis import Result @@ -15,7 +15,7 @@ from ethereum_test_types import Transaction, TransactionReceipt -class ExecutionContext(Enum): +class ExecutionContext(StrEnum): """The execution context in which a test case can fail.""" BLOCK = "Block" diff --git a/src/ethereum_test_specs/static_state/common/compile_yul.py b/src/ethereum_test_specs/static_state/common/compile_yul.py index c4460465138..6620565e51d 100644 --- a/src/ethereum_test_specs/static_state/common/compile_yul.py +++ b/src/ethereum_test_specs/static_state/common/compile_yul.py @@ -1,6 +1,54 @@ """compile yul with arguments.""" import subprocess +from pathlib import Path +from typing import LiteralString + + +def safe_solc_command( + source_file: Path | str, evm_version: str | None = None, optimize: str | None = None +) -> list[str]: + """Safely construct solc command with validated inputs.""" + # Validate source file path + source_path = Path(source_file) + if not source_path.exists(): + raise FileNotFoundError(f"Source file not found: {source_file}") + if source_path.suffix not in (".yul", ".sol"): + raise ValueError(f"Invalid file extension for solc: {source_path.suffix}") + + cmd: list[str] = ["solc"] + + # Add EVM version if provided (validate against known versions) + if evm_version: + valid_versions = { + "homestead", + "tangerineWhistle", + "spuriousDragon", + "byzantium", + "constantinople", + "petersburg", + "istanbul", + "berlin", + "london", + "paris", + "shanghai", + "cancun", + } + if evm_version not in valid_versions: + raise ValueError(f"Invalid EVM version: {evm_version}") + cmd.extend(["--evm-version", evm_version]) + + # Add compilation flags (using literal strings) + strict_assembly: LiteralString = "--strict-assembly" + cmd.append(strict_assembly) + + if optimize is None: + optimize_flag: LiteralString = "--optimize" + yul_opts: LiteralString = "--yul-optimizations=:" + cmd.extend([optimize_flag, yul_opts]) + + cmd.append(str(source_path)) + return cmd def compile_yul(source_file: str, evm_version: str | None = None, optimize: str | None = None): @@ -19,17 +67,7 @@ def compile_yul(source_file: str, evm_version: str | None = None, optimize: str Raises_: Exception: If the solc output contains an error message. """ - cmd = ["solc"] - if evm_version: - cmd.extend(["--evm-version", evm_version]) - - # Choose flags based on whether flag is provided - if optimize is None: - # When flag is not provided, include extra optimization flags - cmd.extend(["--strict-assembly", "--optimize", "--yul-optimizations=:", source_file]) - else: - # Otherwise, omit the optimization flags - cmd.extend(["--strict-assembly", source_file]) + cmd = safe_solc_command(source_file, evm_version, optimize) # Execute the solc command and capture both stdout and stderr result = subprocess.run( diff --git a/src/ethereum_test_tools/code/generators.py b/src/ethereum_test_tools/code/generators.py index 819f66b2afe..7af682ab6c4 100644 --- a/src/ethereum_test_tools/code/generators.py +++ b/src/ethereum_test_tools/code/generators.py @@ -265,7 +265,7 @@ def __new__( return super().__new__(cls, bytecode) -@dataclass(kw_only=True) +@dataclass(kw_only=True, slots=True) class Case: """ Small helper class to represent a single, generic case in a `Switch` cases diff --git a/src/ethereum_test_tools/utility/generators.py b/src/ethereum_test_tools/utility/generators.py index 619a4f4b47f..95c9094e91c 100644 --- a/src/ethereum_test_tools/utility/generators.py +++ b/src/ethereum_test_tools/utility/generators.py @@ -1,7 +1,7 @@ """Test generator decorators.""" import json -from enum import Enum +from enum import StrEnum from pathlib import Path from typing import Dict, Generator, List, Protocol @@ -17,7 +17,7 @@ from ethereum_test_vm import Opcodes as Op -class DeploymentTestType(Enum): +class DeploymentTestType(StrEnum): """Represents the type of deployment test.""" DEPLOY_BEFORE_FORK = "deploy_before_fork" @@ -25,7 +25,7 @@ class DeploymentTestType(Enum): DEPLOY_AFTER_FORK = "deploy_after_fork" -class SystemContractTestType(Enum): +class SystemContractTestType(StrEnum): """Represents the type of system contract test.""" GAS_LIMIT = "system_contract_reaches_gas_limit" @@ -42,7 +42,7 @@ def param(self): ) -class ContractAddressHasBalance(Enum): +class ContractAddressHasBalance(StrEnum): """Represents whether the target deployment test has a balance before deployment.""" ZERO_BALANCE = "zero_balance" diff --git a/src/ethereum_test_types/account_types.py b/src/ethereum_test_types/account_types.py index 589ae931a67..8a7624c3cc7 100644 --- a/src/ethereum_test_types/account_types.py +++ b/src/ethereum_test_types/account_types.py @@ -7,6 +7,7 @@ from ethereum_types.bytes import Bytes20 from ethereum_types.numeric import U256, Bytes32, Uint from pydantic import PrivateAttr +from typing_extensions import Self from ethereum_test_base_types import ( Account, @@ -126,9 +127,9 @@ def get_nonce(self) -> Number: self.nonce = Number(nonce + 1) return nonce - def copy(self) -> "EOA": + def copy(self) -> Self: """Return copy of the EOA.""" - return EOA(Address(self), key=self.key, nonce=self.nonce) + return self.__class__(Address(self), key=self.key, nonce=self.nonce) class Alloc(BaseAlloc): diff --git a/src/ethereum_test_types/trie.py b/src/ethereum_test_types/trie.py index 1caa97621ab..8c97e519568 100644 --- a/src/ethereum_test_types/trie.py +++ b/src/ethereum_test_types/trie.py @@ -13,7 +13,6 @@ Sequence, Tuple, TypeVar, - Union, cast, ) @@ -75,7 +74,7 @@ def encode_account(raw_account_data: FrontierAccount, storage_root: Bytes) -> By bytes.fromhex("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") ) -Node = Union[FrontierAccount, Bytes, Uint, U256, None] +Node = FrontierAccount | Bytes | Uint | U256 | None K = TypeVar("K", bound=Bytes) V = TypeVar( "V", @@ -133,7 +132,7 @@ class BranchNode: value: Extended -InternalNode = Union[LeafNode, ExtensionNode, BranchNode] +InternalNode = LeafNode | ExtensionNode | BranchNode def encode_internal_node(node: Optional[InternalNode]) -> Extended: @@ -146,22 +145,23 @@ def encode_internal_node(node: Optional[InternalNode]) -> Extended: which is encoded to `b""`. """ unencoded: Extended - if node is None: - unencoded = b"" - elif isinstance(node, LeafNode): - unencoded = ( - nibble_list_to_compact(node.rest_of_key, True), - node.value, - ) - elif isinstance(node, ExtensionNode): - unencoded = ( - nibble_list_to_compact(node.key_segment, False), - node.subnode, - ) - elif isinstance(node, BranchNode): - unencoded = list(node.subnodes) + [node.value] - else: - raise AssertionError(f"Invalid internal node type {type(node)}!") + match node: + case None: + unencoded = b"" + case LeafNode(): + unencoded = ( + nibble_list_to_compact(node.rest_of_key, True), + node.value, + ) + case ExtensionNode(): + unencoded = ( + nibble_list_to_compact(node.key_segment, False), + node.subnode, + ) + case BranchNode(): + unencoded = list(node.subnodes) + [node.value] + case _: + raise AssertionError(f"Invalid internal node type {type(node)}!") encoded = rlp.encode(unencoded) if len(encoded) < 32: @@ -176,18 +176,19 @@ def encode_node(node: Node, storage_root: Optional[Bytes] = None) -> Bytes: Currently mostly an unimplemented stub. """ - if isinstance(node, FrontierAccount): - assert storage_root is not None - return encode_account(node, storage_root) - elif isinstance(node, U256): - return rlp.encode(node) - elif isinstance(node, Bytes): - return node - else: - raise AssertionError(f"encoding for {type(node)} is not currently implemented") - - -@dataclass + match node: + case FrontierAccount(): + assert storage_root is not None + return encode_account(node, storage_root) + case U256(): + return rlp.encode(node) + case Bytes(): + return node + case _: + raise AssertionError(f"encoding for {type(node)} is not currently implemented") + + +@dataclass(slots=True) class Trie(Generic[K, V]): """The Merkle Trie.""" diff --git a/src/pytest_plugins/consume/consume.py b/src/pytest_plugins/consume/consume.py index 169ab2d3298..a43d2cdb5fd 100644 --- a/src/pytest_plugins/consume/consume.py +++ b/src/pytest_plugins/consume/consume.py @@ -214,16 +214,16 @@ def from_string(cls, pattern: str) -> "SimLimitBehavior": return cls(pattern=".*", collectonly=True) if pattern.startswith("collectonly:id:"): - literal_id = pattern[len("collectonly:id:") :] + literal_id = pattern.removeprefix("collectonly:id:") if not literal_id: raise ValueError("Empty literal ID provided.") return cls(pattern=cls._escape_id(literal_id), collectonly=True) if pattern.startswith("collectonly:"): - return cls(pattern=pattern[len("collectonly:") :], collectonly=True) + return cls(pattern=pattern.removeprefix("collectonly:"), collectonly=True) if pattern.startswith("id:"): - literal_id = pattern[len("id:") :] + literal_id = pattern.removeprefix("id:") if not literal_id: raise ValueError("Empty literal ID provided.") return cls(pattern=cls._escape_id(literal_id)) @@ -442,4 +442,4 @@ def pytest_collection_modifyitems(items): original_name = item.originalname remove = f"{original_name}[" if item.name.startswith(remove): - item.name = item.name[len(remove) : -1] + item.name = item.name.removeprefix(remove)[:-1] diff --git a/src/pytest_plugins/logging/logging.py b/src/pytest_plugins/logging/logging.py index 1050d44b78d..66d7e58b38a 100644 --- a/src/pytest_plugins/logging/logging.py +++ b/src/pytest_plugins/logging/logging.py @@ -20,7 +20,7 @@ from datetime import datetime, timezone from logging import LogRecord from pathlib import Path -from typing import Any, ClassVar, Optional, Union, cast +from typing import Any, ClassVar, Optional, cast import pytest from _pytest.terminal import TerminalReporter @@ -43,7 +43,7 @@ def verbose( self, msg: object, *args: Any, - exc_info: Union[BaseException, bool, None] = None, + exc_info: BaseException | bool | None = None, stack_info: bool = False, stacklevel: int = 1, extra: Optional[dict[str, Any]] = None, @@ -63,7 +63,7 @@ def fail( self, msg: object, *args: Any, - exc_info: Union[BaseException, bool, None] = None, + exc_info: BaseException | bool | None = None, stack_info: bool = False, stacklevel: int = 1, extra: Optional[dict[str, Any]] = None, @@ -156,8 +156,8 @@ def from_cli(cls, value: str) -> int: def configure_logging( - log_level: Union[int, str] = "INFO", - log_file: Optional[Union[str, Path]] = None, + log_level: int | str = "INFO", + log_file: Optional[str | Path] = None, log_to_stdout: bool = True, log_format: str = "%(asctime)s [%(levelname)s] %(name)s: %(message)s", use_color: Optional[bool] = None, diff --git a/src/pytest_plugins/pytest_hive/pytest_hive.py b/src/pytest_plugins/pytest_hive/pytest_hive.py index 5c8f5e5a1a3..fb29cf33450 100644 --- a/src/pytest_plugins/pytest_hive/pytest_hive.py +++ b/src/pytest_plugins/pytest_hive/pytest_hive.py @@ -270,7 +270,7 @@ def hive_test(request, test_suite: HiveTestSuite): call_out = stdout # If call output starts with setup output, strip it if call_out.startswith(setup_out): - stdout = call_out[len(setup_out) :] + stdout = call_out.removeprefix(setup_out) captured.append( f"# Captured Output from Test {phase.capitalize()}\n\n" diff --git a/tests/istanbul/eip152_blake2/test_blake2.py b/tests/istanbul/eip152_blake2/test_blake2.py index 85ffc825c74..a55818b4b41 100644 --- a/tests/istanbul/eip152_blake2/test_blake2.py +++ b/tests/istanbul/eip152_blake2/test_blake2.py @@ -3,8 +3,6 @@ Test cases for [EIP-152: BLAKE2b compression precompile](https://eips.ethereum.org/EIPS/eip-152). """ -from typing import Union - import pytest from ethereum_test_tools import ( @@ -23,8 +21,7 @@ REFERENCE_SPEC_GIT_PATH = ref_spec_152.git_path REFERENCE_SPEC_VERSION = ref_spec_152.version - -@pytest.mark.ported_from( +pytestmark = pytest.mark.ported_from( [ "https://github.com/ethereum/tests/blob/v13.3/src/GeneralStateTestsFiller/stPreCompiledContracts/blake2BFiller.yml", "https://github.com/ethereum/tests/blob/v13.3/src/GeneralStateTestsFiller/stPreCompiledContracts2/CALLBlake2fFiller.json", @@ -35,7 +32,14 @@ "https://github.com/ethereum/execution-spec-tests/pull/1244", "https://github.com/ethereum/execution-spec-tests/pull/1067", ], + coverage_missed_reason=( + "No longer used opcodes, SUB, GT, ISZERO, AND, CODESIZE, JUMP, some PUSH opcodes." + "Original test calls Blake2b in ConstantinopleFix (activation test), " + "which results in empty account code being triggered." + ), ) + + @pytest.mark.valid_from("Istanbul") @pytest.mark.parametrize("call_opcode", [Op.CALL]) @pytest.mark.parametrize( @@ -388,7 +392,7 @@ def test_blake2b( pre: Alloc, call_opcode: Op, blake2b_contract_bytecode: Bytecode, - data: Union[Blake2bInput, str, bytes], + data: Blake2bInput | str | bytes, output: ExpectedOutput, ): """Test BLAKE2b precompile.""" @@ -429,18 +433,6 @@ def test_blake2b( state_test(env=env, pre=pre, post=post, tx=tx) -@pytest.mark.ported_from( - [ - "https://github.com/ethereum/tests/blob/v13.3/src/GeneralStateTestsFiller/stPreCompiledContracts/blake2BFiller.yml", - "https://github.com/ethereum/tests/blob/v13.3/src/GeneralStateTestsFiller/stPreCompiledContracts2/CALLBlake2fFiller.json", - "https://github.com/ethereum/tests/blob/v13.3/src/GeneralStateTestsFiller/stPreCompiledContracts2/CALLCODEBlake2fFiller.json", - "https://github.com/ethereum/tests/blob/v13.3/src/GeneralStateTestsFiller/stPreCompiledContracts/delegatecall09UndefinedFiller.yml", - ], - pr=[ - "https://github.com/ethereum/execution-spec-tests/pull/1244", - "https://github.com/ethereum/execution-spec-tests/pull/1067", - ], -) @pytest.mark.valid_from("Istanbul") @pytest.mark.parametrize("call_opcode", [Op.CALL, Op.CALLCODE]) @pytest.mark.parametrize("gas_limit", [90_000, 110_000, 200_000]) @@ -517,7 +509,7 @@ def test_blake2b_invalid_gas( call_opcode: Op, blake2b_contract_bytecode: Bytecode, gas_limit: int, - data: Union[Blake2bInput, str, bytes], + data: Blake2bInput | str | bytes, output: ExpectedOutput, ): """Test BLAKE2b precompile invalid calls using different gas limits.""" @@ -554,18 +546,6 @@ def test_blake2b_invalid_gas( state_test(env=env, pre=pre, post=post, tx=tx) -@pytest.mark.ported_from( - [ - "https://github.com/ethereum/tests/blob/v13.3/src/GeneralStateTestsFiller/stPreCompiledContracts/blake2BFiller.yml", - "https://github.com/ethereum/tests/blob/v13.3/src/GeneralStateTestsFiller/stPreCompiledContracts2/CALLBlake2fFiller.json", - "https://github.com/ethereum/tests/blob/v13.3/src/GeneralStateTestsFiller/stPreCompiledContracts2/CALLCODEBlake2fFiller.json", - "https://github.com/ethereum/tests/blob/v13.3/src/GeneralStateTestsFiller/stPreCompiledContracts/delegatecall09UndefinedFiller.yml", - ], - pr=[ - "https://github.com/ethereum/execution-spec-tests/pull/1244", - "https://github.com/ethereum/execution-spec-tests/pull/1067", - ], -) @pytest.mark.valid_from("Istanbul") @pytest.mark.parametrize("call_opcode", [Op.CALL, Op.CALLCODE]) @pytest.mark.parametrize("gas_limit", [Environment().gas_limit, 90_000, 110_000, 200_000]) @@ -638,7 +618,7 @@ def test_blake2b_gas_limit( call_opcode: Op, blake2b_contract_bytecode: Bytecode, gas_limit: int, - data: Union[Blake2bInput, str, bytes], + data: Blake2bInput | str | bytes, output: ExpectedOutput, ): """Test BLAKE2b precompile with different gas limits.""" @@ -676,18 +656,6 @@ def test_blake2b_gas_limit( ) -@pytest.mark.ported_from( - [ - "https://github.com/ethereum/tests/blob/v13.3/src/GeneralStateTestsFiller/stPreCompiledContracts/blake2BFiller.yml", - "https://github.com/ethereum/tests/blob/v13.3/src/GeneralStateTestsFiller/stPreCompiledContracts2/CALLBlake2fFiller.json", - "https://github.com/ethereum/tests/blob/v13.3/src/GeneralStateTestsFiller/stPreCompiledContracts2/CALLCODEBlake2fFiller.json", - "https://github.com/ethereum/tests/blob/v13.3/src/GeneralStateTestsFiller/stPreCompiledContracts/delegatecall09UndefinedFiller.yml", - ], - pr=[ - "https://github.com/ethereum/execution-spec-tests/pull/1244", - "https://github.com/ethereum/execution-spec-tests/pull/1067", - ], -) @pytest.mark.valid_from("Istanbul") @pytest.mark.parametrize("call_opcode", [Op.CALL, Op.CALLCODE]) @pytest.mark.parametrize( @@ -775,7 +743,7 @@ def test_blake2b_large_gas_limit( pre: Alloc, call_opcode: Op, blake2b_contract_bytecode: Bytecode, - data: Union[Blake2bInput, str, bytes], + data: Blake2bInput | str | bytes, output: ExpectedOutput, ): """Test BLAKE2b precompile with large gas limit."""