Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 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
16 changes: 16 additions & 0 deletions packages/testing/src/execution_testing/forks/forks/forks.py
Original file line number Diff line number Diff line change
Expand Up @@ -3444,6 +3444,22 @@ def engine_new_payload_version(
del block_number, timestamp
return 5

@classmethod
def max_code_size(
cls, *, block_number: int = 0, timestamp: int = 0
) -> int:
"""From Amsterdam, max contract code size is 32 KiB. See EIP-7954."""
del block_number, timestamp
return 32 * 1024

@classmethod
def max_initcode_size(
cls, *, block_number: int = 0, timestamp: int = 0
) -> int:
"""From Amsterdam, max initcode size is 64 KiB. See EIP-7954."""
del block_number, timestamp
return 64 * 1024

@classmethod
def engine_execution_payload_block_access_list(
cls, *, block_number: int = 0, timestamp: int = 0
Expand Down
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)."""
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",
)
26 changes: 26 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,26 @@
# 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 | Pre-deploy a contract with `MAX_CODE_SIZE` bytes. Query it with EXTCODESIZE, EXTCODEHASH, and EXTCODECOPY via a single oracle contract. | 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_max_code_size_mainnet` | Deploy a max-size contract on mainnet | Alice deploys a contract with exactly `MAX_CODE_SIZE` bytes of runtime code. | Contract deployed at the expected address. | βœ… 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_max_initcode_size_mainnet` | Max-size initcode creation succeeds on mainnet | Alice sends a creation transaction with exactly `MAX_INITCODE_SIZE` bytes of initcode. | Transaction accepted, contract deployed. | βœ… 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` | Deploy max code with max initcode on mainnet | 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_opcodes_mainnet` | EVM opcodes return correct results for a max-size contract on mainnet | Pre-deploy a contract with `MAX_CODE_SIZE` bytes. Query it with EXTCODESIZE, EXTCODEHASH, and EXTCODECOPY. | Each opcode returns the correct value for the max-size contract. | βœ… Completed |
198 changes: 198 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,198 @@
"""
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("Amsterdam"), pytest.mark.mainnet]


def test_max_code_size_mainnet(
state_test: StateTestFiller,
pre: Alloc,
fork: Fork,
) -> None:
"""Verify end-to-end deployment of a max-size contract on mainnet."""
deploy_code = Op.JUMPDEST * fork.max_code_size()
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 = {create_address: Account(code=deploy_code)}

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


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)


def test_max_initcode_size_mainnet(
state_test: StateTestFiller,
pre: Alloc,
fork: Fork,
) -> None:
"""Verify a CREATE transaction with max-size initcode succeeds."""
initcode = Initcode(
deploy_code=Op.STOP,
initcode_length=fork.max_initcode_size(),
)

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 = {create_address: Account(code=Op.STOP)}

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,
) -> None:
"""Ensure max-size code deploys when initcode is also at max size."""
deploy_code = Op.JUMPDEST * fork.max_code_size()
initcode = Initcode(
deploy_code=deploy_code,
initcode_length=fork.max_initcode_size(),
)

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 = {create_address: Account(code=deploy_code)}

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


def test_max_code_size_opcodes_mainnet(
state_test: StateTestFiller,
pre: Alloc,
fork: Fork,
) -> None:
"""Verify EVM opcodes work for a max-size deployed contract."""
target_code = Op.JUMPDEST * fork.max_code_size()
target = pre.deterministic_deploy_contract(deploy_code=target_code)

alice = pre.fund_eoa()
oracle = pre.deploy_contract(
code=(
Op.SSTORE(0, Op.EXTCODESIZE(target))
+ Op.SSTORE(1, Op.EXTCODEHASH(target))
+ Op.EXTCODECOPY(target, 0, 0, Op.EXTCODESIZE(target))
+ Op.SSTORE(2, Op.SHA3(0, Op.EXTCODESIZE(target)))
)
)

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

post = {
oracle: 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