Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""
EIP-7954: Increase Maximum Contract Size.

Raise the maximum contract code size from 24KiB to 32KiB and initcode size from
48KiB to 64KiB.

https://eips.ethereum.org/EIPS/eip-7954
"""

from ....base_fork import BaseFork


class EIP7954(BaseFork):
"""EIP-7954 class."""

@classmethod
def max_code_size(cls) -> int:
"""Max contract code size is 32 KiB."""
return 32 * 1024

@classmethod
def max_initcode_size(cls) -> int:
"""Max initcode size is 64 KiB."""
return 64 * 1024
2 changes: 2 additions & 0 deletions src/ethereum/forks/amsterdam/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
### Changes
- [EIP-7928: Block-Level Access Lists][EIP-7928]
- [EIP-7954: Increase Maximum Contract Size][EIP-7954]
### Releases
[EIP-7773]: https://eips.ethereum.org/EIPS/eip-7773
[EIP-7928]: https://eips.ethereum.org/EIPS/eip-7928
[EIP-7954]: https://eips.ethereum.org/EIPS/eip-7954
"""

from ethereum.fork_criteria import ForkCriteria, Unscheduled
Expand Down
2 changes: 1 addition & 1 deletion src/ethereum/forks/amsterdam/vm/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
from .runtime import get_valid_jump_destinations

STACK_DEPTH_LIMIT = Uint(1024)
MAX_CODE_SIZE = 0x6000
MAX_CODE_SIZE = 0x8000
MAX_INIT_CODE_SIZE = 2 * MAX_CODE_SIZE


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Tests for [EIP-7954: Increase Maximum Contract Size](https://eips.ethereum.org/EIPS/eip-7954)."""
28 changes: 28 additions & 0 deletions tests/amsterdam/eip7954_increase_max_contract_size/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""Fixtures for the EIP-7954 max contract size tests."""

import pytest
from execution_testing import Address, Alloc, Bytecode, Fork, Op


@pytest.fixture
def max_code_size_contract(
pre: Alloc,
fork: Fork,
) -> tuple[Address, Bytecode]:
"""
Deploy a max-size self-checking contract deterministically.

The contract uses its own ADDRESS to query EXTCODESIZE, EXTCODEHASH,
and EXTCODECOPY on itself, storing results in storage slots 0-2.
Padded with JUMPDESTs to reach the fork's max code size.
"""
logic = (
Op.SSTORE(0, Op.EXTCODESIZE(Op.ADDRESS))
+ Op.SSTORE(1, Op.EXTCODEHASH(Op.ADDRESS))
+ Op.EXTCODECOPY(Op.ADDRESS, 0, 0, Op.EXTCODESIZE(Op.ADDRESS))
+ Op.SSTORE(2, Op.SHA3(0, Op.EXTCODESIZE(Op.ADDRESS)))
+ Op.STOP
)
target_code = logic + Op.JUMPDEST * (fork.max_code_size() - len(logic))
target = pre.deterministic_deploy_contract(deploy_code=target_code)
return target, target_code
17 changes: 17 additions & 0 deletions tests/amsterdam/eip7954_increase_max_contract_size/spec.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""Reference spec for [EIP-7954: Increase Maximum Contract Size](https://eips.ethereum.org/EIPS/eip-7954)."""

from dataclasses import dataclass


@dataclass(frozen=True)
class ReferenceSpec:
"""Reference specification."""

git_path: str
version: str


ref_spec_7954 = ReferenceSpec(
git_path="EIPS/eip-7954.md",
version="b1f5bf8f70ba9306400f5e13313f781c35acc860",
)
23 changes: 23 additions & 0 deletions tests/amsterdam/eip7954_increase_max_contract_size/test_cases.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# EIP-7954 Increase Maximum Contract Size Test Cases

| Function Name | Goal | Setup | Expectation | Status |
|---------------|------|-------|-------------|--------|
| `test_max_code_size` | Enforce new `MAX_CODE_SIZE` boundary for contract creation transactions | Alice deploys contracts with runtime code at the new max and one byte over. | New max: contract deployed. Over max: deployment fails. | ✅ Completed |
| `test_max_code_size_via_create` | Enforce new `MAX_CODE_SIZE` boundary via CREATE/CREATE2 opcodes | Same as above but deployment is done through a factory contract using CREATE and CREATE2. | New max: child contract deployed. Over max: child contract does not exist. | ✅ Completed |
| `test_max_initcode_size` | Enforce new `MAX_INITCODE_SIZE` boundary for contract creation transactions | Alice sends creation transactions with initcode at the new max and one byte over. | New max: transaction accepted, contract deployed. Over max: transaction rejected. | ✅ Completed |
| `test_max_initcode_size_via_create` | Enforce new `MAX_INITCODE_SIZE` boundary via CREATE/CREATE2 opcodes | Same as above but initcode is passed through a factory contract using CREATE and CREATE2. | New max: child contract deployed. Over max: CREATE returns 0, child contract does not exist. | ✅ Completed |
| `test_max_initcode_size_gas_metering` | Verify initcode gas metering at the new max (transaction level) | Alice sends a creation transaction with max-size initcode. Gas limit set to exact intrinsic cost, then one short. | Exact gas: contract deployed. One short: transaction rejected. | ✅ Completed |
| `test_max_initcode_size_gas_metering_via_create` | Verify initcode gas metering at the new max (opcode level) | Caller forwards computed exact gas to a factory that runs CREATE with max-size initcode. Tested with exact gas and one short. | Exact gas: CREATE succeeds, contract deployed. One short: factory runs out of gas. | ✅ Completed |
| `test_max_code_size_deposit_gas` | Verify code deposit gas is charged correctly at the new max | Alice deploys a contract with exactly `MAX_CODE_SIZE` bytes. Gas set to exact deposit cost, then one short. | Exact gas: contract deployed. One short: deployment fails (out of gas during code deposit). | ✅ Completed |
| `test_max_code_size_external_opcodes` | Verify external code opcodes work with max-size contracts | Deterministically pre-deploy a max-size self-checking contract. Call it to run EXTCODESIZE, EXTCODEHASH, and EXTCODECOPY on itself via ADDRESS. | Each opcode returns the correct value for the max-size contract. | ✅ Completed |
| `test_max_code_size_self_opcodes` | Verify self code opcodes work with max-size contracts | Pre-deploy a max-size contract with CODESIZE and CODECOPY checker logic. Call via DELEGATECALL so opcodes operate on the large contract's own code. | CODESIZE returns the correct length, CODECOPY produces the correct hash. | ✅ Completed |
| `test_max_code_size_with_max_initcode` | Deploy max-size code when initcode is also at max size | Alice deploys a contract with `MAX_CODE_SIZE` bytes of runtime code using initcode padded to `MAX_INITCODE_SIZE`. | Contract deployed with the full max-size runtime code. | ✅ Completed |
| `test_max_code_size_fork_transition` | New `MAX_CODE_SIZE` activates exactly at the fork boundary | Before the fork, deploy a contract with the new `MAX_CODE_SIZE` bytes of runtime code. After the fork, attempt the same deployment. | Pre-fork: deployment fails (exceeds old limit). Post-fork: deployment succeeds. | ✅ Completed |
| `test_max_code_size_via_create_fork_transition` | New `MAX_CODE_SIZE` activates at the fork boundary via CREATE/CREATE2 opcodes | Same as above but deployment is done through a factory contract using CREATE and CREATE2. | Pre-fork: child contract does not exist. Post-fork: child contract deployed. | ✅ Completed |
| `test_max_initcode_size_fork_transition` | New `MAX_INITCODE_SIZE` activates exactly at the fork boundary for transactions | Before the fork, send a creation transaction with the new `MAX_INITCODE_SIZE` bytes of initcode. After the fork, send the same transaction. | Pre-fork: block rejected (initcode exceeds old limit). Post-fork: transaction accepted, contract deployed. | ✅ Completed |
| `test_max_initcode_size_via_create_fork_transition` | New `MAX_INITCODE_SIZE` activates at the fork boundary via CREATE/CREATE2 opcodes | Same as above but initcode is passed through a factory contract using CREATE and CREATE2. | Pre-fork: CREATE fails (initcode exceeds old limit). Post-fork: child contract deployed. | ✅ Completed |
| `test_max_code_size_with_max_initcode_fork_transition` | Both new limits activate together at the fork boundary | Before the fork, deploy max code with max initcode. After the fork, attempt the same deployment. | Pre-fork: block rejected (initcode exceeds old limit). Post-fork: contract deployed with max-size runtime code. | ✅ Completed |
| `test_parent_max_code_size_across_fork` | Old `MAX_CODE_SIZE` still works on both sides of the transition | Before and after the fork, deploy a contract with the old `MAX_CODE_SIZE` bytes of runtime code. | Both deployments succeed. The old limit remains valid after the fork. | ✅ Completed |
| `test_over_max_code_size_mainnet` | Deploying above the new limit fails on mainnet | Alice deploys a contract with `MAX_CODE_SIZE + 1` bytes of runtime code. | Contract does not exist (deployment fails during code deposit). | ✅ Completed |
| `test_over_max_initcode_size_mainnet` | Oversized initcode creation is rejected on mainnet | Alice sends a creation transaction with `MAX_INITCODE_SIZE + 1` bytes of initcode. | Transaction rejected. No contract deployed. | ✅ Completed |
| `test_max_code_size_with_max_initcode_mainnet` | Verify opcodes on a max-size contract on mainnet | Call the deterministic max-size self-checking contract. It queries EXTCODESIZE, EXTCODEHASH, and EXTCODECOPY on itself via ADDRESS. | Each opcode returns the correct value for the max-size contract. | ✅ Completed |
119 changes: 119 additions & 0 deletions tests/amsterdam/eip7954_increase_max_contract_size/test_eip_mainnet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
"""
Mainnet tests for
[EIP-7954: Increase Maximum Contract Size](https://eips.ethereum.org/EIPS/eip-7954).
"""

from typing import Any

import pytest
from execution_testing import (
Account,
Alloc,
Fork,
Initcode,
Op,
StateTestFiller,
Transaction,
TransactionException,
compute_create_address,
keccak256,
)

from .spec import ref_spec_7954

REFERENCE_SPEC_GIT_PATH = ref_spec_7954.git_path
REFERENCE_SPEC_VERSION = ref_spec_7954.version

pytestmark = [pytest.mark.valid_at("EIP7954"), pytest.mark.mainnet]


def test_over_max_code_size_mainnet(
state_test: StateTestFiller,
pre: Alloc,
fork: Fork,
) -> None:
"""Verify deployment above the new limit is rejected on mainnet."""
deploy_code = Op.JUMPDEST * (fork.max_code_size() + 1)
initcode = Initcode(deploy_code=deploy_code)

alice = pre.fund_eoa()
create_address = compute_create_address(address=alice, nonce=0)

tx = Transaction(
sender=alice,
to=None,
data=initcode,
gas_limit=fork.transaction_gas_limit_cap(),
)

post: dict[Any, Account | None] = {
create_address: Account.NONEXISTENT,
}

state_test(pre=pre, tx=tx, post=post)


@pytest.mark.exception_test
def test_over_max_initcode_size_mainnet(
state_test: StateTestFiller,
pre: Alloc,
fork: Fork,
) -> None:
"""Verify a CREATE transaction over the new initcode limit is rejected."""
initcode = Initcode(
deploy_code=Op.STOP,
initcode_length=fork.max_initcode_size() + 1,
)

alice = pre.fund_eoa()
create_address = compute_create_address(address=alice, nonce=0)

tx = Transaction(
sender=alice,
to=None,
data=initcode,
gas_limit=fork.transaction_gas_limit_cap(),
error=TransactionException.INITCODE_SIZE_EXCEEDED,
)

post: dict[Any, Account | None] = {
create_address: Account.NONEXISTENT,
}

state_test(pre=pre, tx=tx, post=post)


def test_max_code_size_with_max_initcode_mainnet(
state_test: StateTestFiller,
pre: Alloc,
fork: Fork,
max_code_size_contract: tuple,
) -> None:
"""
Verify max-size contract works on mainnet.

Calls the deterministic max-size contract which checks EXTCODESIZE,
EXTCODEHASH, and EXTCODECOPY on itself. The contract bytecode is
the same used for deployment tests, padded to max code size.
"""
target, target_code = max_code_size_contract

alice = pre.fund_eoa()

tx = Transaction(
sender=alice,
to=target,
gas_limit=fork.transaction_gas_limit_cap(),
)

post = {
target: Account(
storage={
0: len(target_code),
1: keccak256(bytes(target_code)),
2: keccak256(bytes(target_code)),
}
)
}

state_test(pre=pre, tx=tx, post=post)
Loading
Loading