Skip to content
2 changes: 2 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ Users can select any of the artifacts depending on their testing needs for their
- ✨ [EIP-7934](https://eips.ethereum.org/EIPS/eip-7934): Add test cases for the block RLP max limit of 10MiB ([#1730](https://github.com/ethereum/execution-spec-tests/pull/1730)).
- ✨ [EIP-7939](https://eips.ethereum.org/EIPS/eip-7939) Add count leading zeros (CLZ) opcode tests for Osaka ([#1733](https://github.com/ethereum/execution-spec-tests/pull/1733)).
- ✨ [EIP-7918](https://eips.ethereum.org/EIPS/eip-7918): Blob base fee bounded by execution cost test cases (initial), includes some adjustments to [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844) tests ([#1685](https://github.com/ethereum/execution-spec-tests/pull/1685)).
- ✨ [EIP-7907](https://eips.ethereum.org/EIPS/eip-7907): Initial meter contract code size and increase limit tests cases ([#1777](https://github.com/ethereum/execution-spec-tests/pull/1777)).
- πŸ”€ [EIP-3860](https://eips.ethereum.org/EIPS/eip-3860) tests cases updated for [EIP-7907](https://eips.ethereum.org/EIPS/eip-7907) ([#1777](https://github.com/ethereum/execution-spec-tests/pull/1777)).

## [v4.5.0](https://github.com/ethereum/execution-spec-tests/releases/tag/v4.5.0) - 2025-05-14

Expand Down
2 changes: 1 addition & 1 deletion eels_resolutions.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,6 @@
"Osaka": {
"git_url": "https://github.com/spencer-tb/execution-specs.git",
"branch": "forks/osaka",
"commit": "99734284b89766883ecb680e57b07fbca47da51b"
"commit": "bc829598ff1923f9215a6a407ef74621077fd3bb"
}
}
34 changes: 34 additions & 0 deletions src/ethereum_test_forks/base_fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,18 @@ def __call__(
pass


class LargeContractAccessGasCalculator(Protocol):
"""A protocol to calculate the extra gas cost of accessing a large contract at a given fork."""

def __call__(
self,
*,
size: int,
) -> int:
"""Return the extra gas cost of accessing a large contract given its size."""
pass


class BaseForkMeta(ABCMeta):
"""Metaclass for BaseFork."""

Expand Down Expand Up @@ -275,6 +287,28 @@ def excess_blob_gas_calculator(
"""Return a callable that calculates the excess blob gas for a block at a given fork."""
pass

@classmethod
@abstractmethod
def large_contract_size(cls, block_number: int = 0, timestamp: int = 0) -> int | None:
"""
Return the size of a large contract at a given fork, at which the extra gas cost of
accessing a large contract is applied.

If `None`, the extra gas cost of accessing a large contract does not exist.
"""
pass

@classmethod
@abstractmethod
def large_contract_access_gas_calculator(
cls, block_number: int = 0, timestamp: int = 0
) -> LargeContractAccessGasCalculator:
"""
Return a callable that calculates the extra gas cost of accessing a large contract at a
given fork.
"""
pass

@classmethod
@abstractmethod
def min_base_fee_per_blob_gas(cls, block_number: int = 0, timestamp: int = 0) -> int:
Expand Down
65 changes: 59 additions & 6 deletions src/ethereum_test_forks/forks/forks.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@
BlobGasPriceCalculator,
CalldataGasCalculator,
ExcessBlobGasCalculator,
LargeContractAccessGasCalculator,
MemoryExpansionGasCalculator,
TransactionDataFloorCostCalculator,
TransactionIntrinsicCostCalculator,
)
from ..gas_costs import GasCosts
from .helpers import ceiling_division, fake_exponential
from .helpers import ceil32, ceiling_division, fake_exponential

CURRENT_FILE = Path(realpath(__file__))
CURRENT_FOLDER = CURRENT_FILE.parent
Expand Down Expand Up @@ -225,6 +226,17 @@ def excess_blob_gas_calculator(
"""Return a callable that calculates the excess blob gas for a block at a given fork."""
raise NotImplementedError(f"Excess blob gas calculator is not supported in {cls.name()}")

@classmethod
def large_contract_access_gas_calculator(
cls, block_number: int = 0, timestamp: int = 0
) -> LargeContractAccessGasCalculator:
"""At Frontier, there is no extra gas cost of accessing a large contract."""

def fn(*, size: int) -> int:
return 0

return fn

@classmethod
def min_base_fee_per_blob_gas(cls, block_number: int = 0, timestamp: int = 0) -> int:
"""Return the amount of blob gas used per blob at a given fork."""
Expand Down Expand Up @@ -385,17 +397,25 @@ def max_code_size(cls) -> int:
"""However, the default is set to the limit of EIP-170 (Spurious Dragon)"""
return 0x6000

@classmethod
def max_stack_height(cls) -> int:
"""At genesis, the maximum stack height is 1024."""
return 1024

@classmethod
def max_initcode_size(cls) -> int:
"""At genesis, there is no upper bound for initcode size."""
"""However, the default is set to the limit of EIP-3860 (Shanghai)"""
return 0xC000

@classmethod
def large_contract_size(cls, block_number: int = 0, timestamp: int = 0) -> int | None:
"""
Return the size of a large contract at a given fork, at which the extra gas cost of
accessing a large contract is applied.
"""
return None

@classmethod
def max_stack_height(cls) -> int:
"""At genesis, the maximum stack height is 1024."""
return 1024

@classmethod
def call_opcodes(
cls, block_number: int = 0, timestamp: int = 0
Expand Down Expand Up @@ -1389,6 +1409,39 @@ def precompiles(cls, block_number: int = 0, timestamp: int = 0) -> List[Address]
"""
return [Address(0x100)] + super(Osaka, cls).precompiles(block_number, timestamp)

@classmethod
def max_code_size(cls) -> int:
"""At Osaka, the maximum code size is 256KB."""
return 0x40000

@classmethod
def max_initcode_size(cls) -> int:
"""At Osaka, the maximum initcode size is 512KB."""
return 0x80000

@classmethod
def large_contract_size(cls, block_number: int = 0, timestamp: int = 0) -> int | None:
"""
Return the size of a large contract at a given fork, at which the extra gas cost of
accessing a large contract is applied.
"""
return 0x6000

@classmethod
def large_contract_access_gas_calculator(
cls, block_number: int = 0, timestamp: int = 0
) -> LargeContractAccessGasCalculator:
"""At Osaka, the extra gas cost of accessing a large contract is modified."""
large_contract_floor_size = cls.large_contract_size(block_number, timestamp)
assert large_contract_floor_size is not None, "Large contract floor size is required"
gas_init_code_word_cost = 2

def fn(*, size: int) -> int:
excess_contract_size = max(0, size - large_contract_floor_size)
return ceil32(excess_contract_size) * gas_init_code_word_cost // 32

return fn

@classmethod
def excess_blob_gas_calculator(
cls, block_number: int = 0, timestamp: int = 0
Expand Down
10 changes: 10 additions & 0 deletions src/ethereum_test_forks/forks/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,13 @@ def fake_exponential(factor: int, numerator: int, denominator: int) -> int:
numerator_accumulator = (numerator_accumulator * numerator) // (denominator * i)
i += 1
return output // denominator


def ceil32(value: int) -> int:
"""Convert a unsigned integer to the next closest multiple of 32."""
ceiling = 32
remainder = value % ceiling
if remainder == 0:
return value
else:
return value + ceiling - remainder
4 changes: 2 additions & 2 deletions src/ethereum_test_tools/code/generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ class CodeGasMeasure(Bytecode):
To be considered when subtracting the value of the previous GAS operation,
and to be popped at the end of the execution.
"""
sstore_key: int | Bytes
sstore_key: int | Bytes | Bytecode
"""
Storage key to save the gas used.
"""
Expand All @@ -160,7 +160,7 @@ def __new__(
code: Bytecode,
overhead_cost: int = 0,
extra_stack_items: int = 0,
sstore_key: int | Bytes = 0,
sstore_key: int | Bytes | Bytecode = 0,
stop: bool = True,
):
"""Assemble the bytecode that measures gas usage."""
Expand Down
1 change: 1 addition & 0 deletions tests/osaka/eip7907_meter_contract_code_size/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Cross-client EIP-7907 Tests."""
54 changes: 54 additions & 0 deletions tests/osaka/eip7907_meter_contract_code_size/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""Fixtures for EIP-7907 meter contract code size tests."""

import pytest

from ethereum_test_forks import Fork
from ethereum_test_tools import (
Address,
Alloc,
Bytecode,
)
from ethereum_test_tools.vm.opcode import Opcodes as Op
from ethereum_test_types import Environment

from .helpers import create_large_contract


@pytest.fixture
def env() -> Environment:
"""Environment fixture with sufficient gas limit for large contract tests."""
return Environment(gas_limit=30_000_000)


@pytest.fixture
def large_contract_code() -> Bytecode:
"""Return the default large contract code."""
return Op.STOP


@pytest.fixture
def large_contract_size(fork: Fork) -> int:
"""Return the minimum contract size that triggers the large contract access gas."""
large_contract_size = fork.large_contract_size()
if large_contract_size is None:
return fork.max_code_size()
else:
return large_contract_size + 1


@pytest.fixture
def large_contract_bytecode(
large_contract_size: int,
large_contract_code: Bytecode | None,
) -> bytes:
"""Return the default large contract code."""
return create_large_contract(size=large_contract_size, prefix=large_contract_code)


@pytest.fixture
def large_contract_address(
pre: Alloc,
large_contract_bytecode: bytes,
) -> Address:
"""Create a large contract address."""
return pre.deploy_contract(large_contract_bytecode)
15 changes: 15 additions & 0 deletions tests/osaka/eip7907_meter_contract_code_size/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""Helper functions for the EIP-7907 meter contract code size tests."""

from ethereum_test_tools import Bytecode


def create_large_contract(
*,
size: int,
padding_byte: bytes = b"\0",
prefix: Bytecode | None = None,
) -> bytes:
"""Create a large contract with the given size and prefix."""
if prefix is None:
prefix = Bytecode()
return bytes(prefix + padding_byte * (size - len(prefix)))
28 changes: 28 additions & 0 deletions tests/osaka/eip7907_meter_contract_code_size/spec.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""Defines EIP-7907 specification constants and functions."""

from dataclasses import dataclass


@dataclass(frozen=True)
class ReferenceSpec:
"""Defines the reference spec version and git path."""

git_path: str
version: str


ref_spec_7907 = ReferenceSpec("EIPS/eip-7907.md", "d758026fc3bd5ac21b652e73d244dee803b1fe44")


@dataclass(frozen=True)
class Spec:
"""
Reference constants from EIP-7907 specification.

Note: Tests should use fork methods for actual values, not these constants directly.
"""

MAX_CODE_SIZE = 0x40000
MAX_INIT_CODE_SIZE = 0x80000
LARGE_CONTRACT_THRESHOLD = 0x6000
GAS_INIT_CODE_WORD_COST = 2
Loading
Loading