From 4f8bab885252757a4a4a0e9eae3bfcab88d53b47 Mon Sep 17 00:00:00 2001 From: raxhvl <10168946+raxhvl@users.noreply.github.com> Date: Wed, 25 Feb 2026 20:15:04 +0100 Subject: [PATCH 1/5] =?UTF-8?q?=E2=9C=A8=20feat(specs,tests):=20Implement?= =?UTF-8?q?=20EIP-7954=20(#2276)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * โœจ feat: Tests for new contract size * โœจ feat: Gas metering of initcode * โœจ feat: Transition, Mainnet tests * ๐Ÿงน chore: Names * fix: use gas constant instead of hard-coded number * fix: remove references to old_max that are not in tests * fix: align with other forks; use 2*code_size for initcode size * chore: fix lint * refactor: simplify gas calculation with gas cost API; add post check * refactor: use opcode metadata for gas calc * feat(test): add max_code with max_init_code in same test * refactor: simplify calls to compute_create_address; no conditional necessary * ๐Ÿงน chore: Simplify docstring * ๐Ÿงน chore: Simplify opcode based testing * ๐Ÿงน chore: Better names * ๐Ÿงน chore: types * version Co-authored-by: felipe * feat(test): use deterministic deploy for similar contracts * refactor: use same max-size self-checking contract for tests - DRY max size contract for mainnet by including the superset in one and removing subset tests --------- Co-authored-by: raxhvl Co-authored-by: fselmo --- .../forks/forks/eips/amsterdam/eip_7954.py | 24 + src/ethereum/forks/amsterdam/__init__.py | 2 + .../forks/amsterdam/vm/interpreter.py | 2 +- .../__init__.py | 1 + .../conftest.py | 28 ++ .../spec.py | 17 + .../test_cases.md | 23 + .../test_eip_mainnet.py | 119 +++++ .../test_fork_transition.py | 410 ++++++++++++++++++ .../test_max_code_size.py | 272 ++++++++++++ .../test_max_initcode_size.py | 291 +++++++++++++ 11 files changed, 1188 insertions(+), 1 deletion(-) create mode 100644 packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_7954.py create mode 100644 tests/amsterdam/eip7954_increase_max_contract_size/__init__.py create mode 100644 tests/amsterdam/eip7954_increase_max_contract_size/conftest.py create mode 100644 tests/amsterdam/eip7954_increase_max_contract_size/spec.py create mode 100644 tests/amsterdam/eip7954_increase_max_contract_size/test_cases.md create mode 100644 tests/amsterdam/eip7954_increase_max_contract_size/test_eip_mainnet.py create mode 100644 tests/amsterdam/eip7954_increase_max_contract_size/test_fork_transition.py create mode 100644 tests/amsterdam/eip7954_increase_max_contract_size/test_max_code_size.py create mode 100644 tests/amsterdam/eip7954_increase_max_contract_size/test_max_initcode_size.py diff --git a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_7954.py b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_7954.py new file mode 100644 index 00000000000..b5c23ce2752 --- /dev/null +++ b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_7954.py @@ -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 diff --git a/src/ethereum/forks/amsterdam/__init__.py b/src/ethereum/forks/amsterdam/__init__.py index e6f3e9476a0..e47bb43c1a3 100644 --- a/src/ethereum/forks/amsterdam/__init__.py +++ b/src/ethereum/forks/amsterdam/__init__.py @@ -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 diff --git a/src/ethereum/forks/amsterdam/vm/interpreter.py b/src/ethereum/forks/amsterdam/vm/interpreter.py index 80b30bd7ab2..f709d603770 100644 --- a/src/ethereum/forks/amsterdam/vm/interpreter.py +++ b/src/ethereum/forks/amsterdam/vm/interpreter.py @@ -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 diff --git a/tests/amsterdam/eip7954_increase_max_contract_size/__init__.py b/tests/amsterdam/eip7954_increase_max_contract_size/__init__.py new file mode 100644 index 00000000000..7ea8f8276b3 --- /dev/null +++ b/tests/amsterdam/eip7954_increase_max_contract_size/__init__.py @@ -0,0 +1 @@ +"""Tests for [EIP-7954: Increase Maximum Contract Size](https://eips.ethereum.org/EIPS/eip-7954).""" diff --git a/tests/amsterdam/eip7954_increase_max_contract_size/conftest.py b/tests/amsterdam/eip7954_increase_max_contract_size/conftest.py new file mode 100644 index 00000000000..78cb66ed14d --- /dev/null +++ b/tests/amsterdam/eip7954_increase_max_contract_size/conftest.py @@ -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 diff --git a/tests/amsterdam/eip7954_increase_max_contract_size/spec.py b/tests/amsterdam/eip7954_increase_max_contract_size/spec.py new file mode 100644 index 00000000000..7dac2d83625 --- /dev/null +++ b/tests/amsterdam/eip7954_increase_max_contract_size/spec.py @@ -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", +) diff --git a/tests/amsterdam/eip7954_increase_max_contract_size/test_cases.md b/tests/amsterdam/eip7954_increase_max_contract_size/test_cases.md new file mode 100644 index 00000000000..68d665d0c82 --- /dev/null +++ b/tests/amsterdam/eip7954_increase_max_contract_size/test_cases.md @@ -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 | diff --git a/tests/amsterdam/eip7954_increase_max_contract_size/test_eip_mainnet.py b/tests/amsterdam/eip7954_increase_max_contract_size/test_eip_mainnet.py new file mode 100644 index 00000000000..c37a919bb18 --- /dev/null +++ b/tests/amsterdam/eip7954_increase_max_contract_size/test_eip_mainnet.py @@ -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("Amsterdam"), 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) diff --git a/tests/amsterdam/eip7954_increase_max_contract_size/test_fork_transition.py b/tests/amsterdam/eip7954_increase_max_contract_size/test_fork_transition.py new file mode 100644 index 00000000000..29042509f8b --- /dev/null +++ b/tests/amsterdam/eip7954_increase_max_contract_size/test_fork_transition.py @@ -0,0 +1,410 @@ +""" +Fork transition tests for +[EIP-7954: Increase Maximum Contract Size](https://eips.ethereum.org/EIPS/eip-7954). + +Tests that the new max code size and initcode size limits activate +exactly at the Amsterdam fork boundary (timestamp 15,000). +""" + +from typing import Any + +import pytest +from execution_testing import ( + Account, + Alloc, + Block, + BlockchainTestFiller, + Fork, + Initcode, + Op, + Transaction, + TransactionException, + compute_create_address, +) + +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_transition_to("Amsterdam") + +CREATE2_SALT = 0xC0FFEE + + +def test_max_code_size_fork_transition( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """Ensure the new max code size limit activates at the fork boundary.""" + code_size = fork.max_code_size() + deploy_code = Op.JUMPDEST * code_size + initcode = Initcode(deploy_code=deploy_code) + + alice = pre.fund_eoa() + bob = pre.fund_eoa() + + create_address_pre = compute_create_address(address=alice, nonce=0) + create_address_post = compute_create_address(address=bob, nonce=0) + + blocks = [ + Block( + timestamp=14_999, + txs=[ + Transaction( + sender=alice, + to=None, + data=initcode, + gas_limit=fork.transaction_gas_limit_cap(), + ) + ], + ), + Block( + timestamp=15_000, + txs=[ + Transaction( + sender=bob, + to=None, + data=initcode, + gas_limit=fork.transaction_gas_limit_cap(), + ) + ], + ), + ] + + post: dict[Any, Account | None] = { + create_address_pre: Account.NONEXISTENT, + create_address_post: Account(code=deploy_code), + } + + blockchain_test(pre=pre, blocks=blocks, post=post) + + +@pytest.mark.with_all_create_opcodes() +def test_max_code_size_via_create_fork_transition( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + create_opcode: Op, +) -> None: + """Ensure the new max code size limit activates at the fork via opcodes.""" + code_size = fork.max_code_size() + deploy_code = Op.JUMPDEST * code_size + initcode = Initcode(deploy_code=deploy_code) + initcode_bytes = bytes(initcode) + + alice = pre.fund_eoa() + bob = pre.fund_eoa() + + create_call = ( + create_opcode( + value=0, offset=0, size=Op.CALLDATASIZE, salt=CREATE2_SALT + ) + if create_opcode == Op.CREATE2 + else create_opcode(value=0, offset=0, size=Op.CALLDATASIZE) + ) + + factory_code = ( + Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + + Op.SSTORE(0, create_call) + + Op.STOP + ) + + factory_pre = pre.deploy_contract(factory_code) + factory_post = pre.deploy_contract(factory_code) + + create_address_pre = compute_create_address( + address=factory_pre, + nonce=1, + salt=CREATE2_SALT, + initcode=initcode, + opcode=create_opcode, + ) + create_address_post = compute_create_address( + address=factory_post, + nonce=1, + salt=CREATE2_SALT, + initcode=initcode, + opcode=create_opcode, + ) + + blocks = [ + Block( + timestamp=14_999, + txs=[ + Transaction( + sender=alice, + to=factory_pre, + data=initcode_bytes, + gas_limit=fork.transaction_gas_limit_cap(), + ) + ], + ), + Block( + timestamp=15_000, + txs=[ + Transaction( + sender=bob, + to=factory_post, + data=initcode_bytes, + gas_limit=fork.transaction_gas_limit_cap(), + ) + ], + ), + ] + + post: dict[Any, Account | None] = { + create_address_pre: Account.NONEXISTENT, + create_address_post: Account(code=deploy_code), + } + + blockchain_test(pre=pre, blocks=blocks, post=post) + + +@pytest.mark.exception_test +def test_max_initcode_size_fork_transition( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """Ensure the new max initcode size limit activates exactly at the fork.""" + initcode = Initcode( + deploy_code=Op.STOP, + initcode_length=fork.max_initcode_size(), + ) + + alice = pre.fund_eoa() + bob = pre.fund_eoa() + + create_address_post = compute_create_address(address=bob, nonce=0) + + initcode_too_large = TransactionException.INITCODE_SIZE_EXCEEDED + + blocks = [ + # Pre-fork: initcode at the new max exceeds the parent fork's limit, + # so the tx is rejected and the block is invalid. + Block( + timestamp=14_999, + txs=[ + Transaction( + sender=alice, + to=None, + data=initcode, + gas_limit=fork.transaction_gas_limit_cap(), + error=initcode_too_large, + ) + ], + exception=initcode_too_large, + ), + # Post-fork: the new limit is in effect, tx succeeds. + Block( + timestamp=15_000, + txs=[ + Transaction( + sender=bob, + to=None, + data=initcode, + gas_limit=fork.transaction_gas_limit_cap(), + ) + ], + ), + ] + + post: dict[Any, Account | None] = { + create_address_post: Account(code=Op.STOP), + } + + blockchain_test(pre=pre, blocks=blocks, post=post) + + +@pytest.mark.with_all_create_opcodes() +def test_max_initcode_size_via_create_fork_transition( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + create_opcode: Op, +) -> None: + """Ensure the new max initcode size limit activates at fork via opcodes.""" + initcode = Initcode( + deploy_code=Op.STOP, + initcode_length=fork.max_initcode_size(), + ) + initcode_bytes = bytes(initcode) + + alice = pre.fund_eoa() + bob = pre.fund_eoa() + + create_call = ( + create_opcode( + value=0, offset=0, size=Op.CALLDATASIZE, salt=CREATE2_SALT + ) + if create_opcode == Op.CREATE2 + else create_opcode(value=0, offset=0, size=Op.CALLDATASIZE) + ) + + factory_code = ( + Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + + Op.SSTORE(0, create_call) + + Op.STOP + ) + + factory_pre = pre.deploy_contract(factory_code) + factory_post = pre.deploy_contract(factory_code) + + create_address_pre = compute_create_address( + address=factory_pre, + nonce=1, + salt=CREATE2_SALT, + initcode=initcode, + opcode=create_opcode, + ) + create_address_post = compute_create_address( + address=factory_post, + nonce=1, + salt=CREATE2_SALT, + initcode=initcode, + opcode=create_opcode, + ) + + blocks = [ + Block( + timestamp=14_999, + txs=[ + Transaction( + sender=alice, + to=factory_pre, + data=initcode_bytes, + gas_limit=fork.transaction_gas_limit_cap(), + ) + ], + ), + Block( + timestamp=15_000, + txs=[ + Transaction( + sender=bob, + to=factory_post, + data=initcode_bytes, + gas_limit=fork.transaction_gas_limit_cap(), + ) + ], + ), + ] + + # Pre-fork: CREATE returns 0 (initcode exceeds parent fork limit) + # Post-fork: CREATE succeeds + post: dict[Any, Account | None] = { + factory_pre: Account(storage={0: 0}), + create_address_pre: Account.NONEXISTENT, + factory_post: Account(storage={0: create_address_post}), + create_address_post: Account(code=Op.STOP), + } + + blockchain_test(pre=pre, blocks=blocks, post=post) + + +@pytest.mark.exception_test +def test_max_code_size_with_max_initcode_fork_transition( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """Ensure max code + max initcode activates at the fork boundary.""" + deploy_code = Op.JUMPDEST * fork.max_code_size() + initcode = Initcode( + deploy_code=deploy_code, + initcode_length=fork.max_initcode_size(), + ) + + alice = pre.fund_eoa() + bob = pre.fund_eoa() + + create_address_post = compute_create_address(address=bob, nonce=0) + + initcode_too_large = TransactionException.INITCODE_SIZE_EXCEEDED + + blocks = [ + Block( + timestamp=14_999, + txs=[ + Transaction( + sender=alice, + to=None, + data=initcode, + gas_limit=fork.transaction_gas_limit_cap(), + error=initcode_too_large, + ) + ], + exception=initcode_too_large, + ), + Block( + timestamp=15_000, + txs=[ + Transaction( + sender=bob, + to=None, + data=initcode, + gas_limit=fork.transaction_gas_limit_cap(), + ) + ], + ), + ] + + post: dict[Any, Account | None] = { + create_address_post: Account(code=deploy_code), + } + + blockchain_test(pre=pre, blocks=blocks, post=post) + + +def test_parent_max_code_size_across_fork( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """Ensure previous max code size works after transition.""" + parent = fork.parent() + assert parent is not None, "Parent fork must be defined for this test" + + code_size = parent.max_code_size() + deploy_code = Op.JUMPDEST * code_size + initcode = Initcode(deploy_code=deploy_code) + + alice = pre.fund_eoa() + bob = pre.fund_eoa() + + create_address_pre = compute_create_address(address=alice, nonce=0) + create_address_post = compute_create_address(address=bob, nonce=0) + + blocks = [ + Block( + timestamp=14_999, + txs=[ + Transaction( + sender=alice, + to=None, + data=initcode, + gas_limit=fork.transaction_gas_limit_cap(), + ) + ], + ), + Block( + timestamp=15_000, + txs=[ + Transaction( + sender=bob, + to=None, + data=initcode, + gas_limit=fork.transaction_gas_limit_cap(), + ) + ], + ), + ] + + post: dict[Any, Account | None] = { + create_address_pre: Account(code=deploy_code), + create_address_post: Account(code=deploy_code), + } + + blockchain_test(pre=pre, blocks=blocks, post=post) diff --git a/tests/amsterdam/eip7954_increase_max_contract_size/test_max_code_size.py b/tests/amsterdam/eip7954_increase_max_contract_size/test_max_code_size.py new file mode 100644 index 00000000000..737831cc0fb --- /dev/null +++ b/tests/amsterdam/eip7954_increase_max_contract_size/test_max_code_size.py @@ -0,0 +1,272 @@ +""" +Test [EIP-7954: Increase Maximum Contract Size](https://eips.ethereum.org/EIPS/eip-7954). +""" + +from typing import Any, Callable + +import pytest +from execution_testing import ( + Account, + Alloc, + Fork, + Initcode, + Op, + StateTestFiller, + Transaction, + 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_from("Amsterdam") + +CREATE2_SALT = 0xC0FFEE + +DEPLOY_CODE_SIZE_PARAMS = [ + pytest.param(lambda f: f.max_code_size(), id="at_max"), + pytest.param(lambda f: f.max_code_size() + 1, id="over_max"), +] + + +@pytest.mark.parametrize("deploy_code_size", DEPLOY_CODE_SIZE_PARAMS) +def test_max_code_size( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + deploy_code_size: Callable[[Fork], int], +) -> None: + """Ensure the new max code size boundary is enforced.""" + code_size = deploy_code_size(fork) + deploy_code = Op.JUMPDEST * code_size + + alice = pre.fund_eoa() + initcode = Initcode(deploy_code=deploy_code) + 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] = {} + if code_size <= fork.max_code_size(): + post[create_address] = Account(code=deploy_code) + else: + post[create_address] = Account.NONEXISTENT + + state_test(pre=pre, tx=tx, post=post) + + +@pytest.mark.parametrize("deploy_code_size", DEPLOY_CODE_SIZE_PARAMS) +@pytest.mark.with_all_create_opcodes() +def test_max_code_size_via_create( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + deploy_code_size: Callable[[Fork], int], + create_opcode: Op, +) -> None: + """Ensure the new max code size boundary is enforced via create opcodes.""" + code_size = deploy_code_size(fork) + deploy_code = Op.JUMPDEST * code_size + initcode = Initcode(deploy_code=deploy_code) + initcode_bytes = bytes(initcode) + + alice = pre.fund_eoa() + + create_call = ( + create_opcode( + value=0, offset=0, size=Op.CALLDATASIZE, salt=CREATE2_SALT + ) + if create_opcode == Op.CREATE2 + else create_opcode(value=0, offset=0, size=Op.CALLDATASIZE) + ) + + factory_code = ( + Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + + Op.SSTORE(0, create_call) + + Op.STOP + ) + + factory = pre.deploy_contract(factory_code) + + create_address = compute_create_address( + address=factory, + nonce=1, + salt=CREATE2_SALT, + initcode=initcode, + opcode=create_opcode, + ) + + tx = Transaction( + sender=alice, + to=factory, + data=initcode_bytes, + gas_limit=fork.transaction_gas_limit_cap(), + ) + + created = code_size <= fork.max_code_size() + post: dict[Any, Account | None] = { + factory: Account(storage={0: create_address if created else 0}), + } + if created: + post[create_address] = Account(code=deploy_code) + else: + post[create_address] = Account.NONEXISTENT + + state_test(pre=pre, tx=tx, post=post) + + +@pytest.mark.parametrize( + "gas_shortfall", + [ + pytest.param(0, id="exact_gas"), + pytest.param(1, id="short_one_gas"), + ], +) +def test_max_code_size_deposit_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + gas_shortfall: int, +) -> None: + """Ensure code deposit gas is charged correctly at the new max.""" + 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) + + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + calldata=initcode, + contract_creation=True, + return_cost_deducted_prior_execution=True, + ) + + tx = Transaction( + sender=alice, + to=None, + data=initcode, + gas_limit=( + intrinsic_gas + + initcode.execution_gas(fork) + + initcode.deployment_gas(fork) + - gas_shortfall + ), + ) + # With shortfall, code deposit OOGs: tx succeeds but + # contract is not deployed + post = { + create_address: Account(code=deploy_code) + if not gas_shortfall + else Account.NONEXISTENT, + } + + state_test(pre=pre, tx=tx, post=post) + + +def test_max_code_size_with_max_initcode( + 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_external_opcodes( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + max_code_size_contract: tuple, +) -> None: + """Ensure external code opcodes work with the new max contract 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) + + +def test_max_code_size_self_opcodes( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Ensure self code opcodes work with the new max contract size. + + Tested via DELEGATECALL so opcodes operate on the large + contract's own code while writing results to the caller's + storage. + """ + logic = ( + Op.SSTORE(0, Op.CODESIZE) + + Op.CODECOPY(0, 0, Op.CODESIZE) + + Op.SSTORE(1, Op.SHA3(0, Op.CODESIZE)) + + Op.STOP + ) + target_code = logic + Op.JUMPDEST * (fork.max_code_size() - len(logic)) + target = pre.deterministic_deploy_contract(deploy_code=target_code) + + alice = pre.fund_eoa() + oracle = pre.deploy_contract( + code=Op.DELEGATECALL(gas=Op.GAS, address=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)), + } + ) + } + + state_test(pre=pre, tx=tx, post=post) diff --git a/tests/amsterdam/eip7954_increase_max_contract_size/test_max_initcode_size.py b/tests/amsterdam/eip7954_increase_max_contract_size/test_max_initcode_size.py new file mode 100644 index 00000000000..937ed015352 --- /dev/null +++ b/tests/amsterdam/eip7954_increase_max_contract_size/test_max_initcode_size.py @@ -0,0 +1,291 @@ +""" +Test [EIP-7954: Increase Maximum Contract Size](https://eips.ethereum.org/EIPS/eip-7954). + +Tests for the increased maximum initcode size (64 KiB). +""" + +from typing import Any, Callable + +import pytest +from execution_testing import ( + Account, + Alloc, + Fork, + Initcode, + Op, + StateTestFiller, + Transaction, + TransactionException, + compute_create_address, +) + +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_from("Amsterdam") + +CREATE2_SALT = 0xC0FFEE + +INITCODE_SIZE_PARAMS = [ + pytest.param(lambda f: f.max_initcode_size(), id="at_max"), + pytest.param(lambda f: f.max_initcode_size() + 1, id="over_max"), +] + +TX_INITCODE_SIZE_PARAMS = [ + pytest.param(lambda f: f.max_initcode_size(), id="at_max"), + pytest.param( + lambda f: f.max_initcode_size() + 1, + id="over_max", + marks=pytest.mark.exception_test, + ), +] + + +@pytest.mark.parametrize("initcode_size", TX_INITCODE_SIZE_PARAMS) +def test_max_initcode_size( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + initcode_size: Callable[[Fork], int], +) -> None: + """Ensure the new max initcode size is enforced for transactions.""" + size = initcode_size(fork) + initcode = Initcode( + deploy_code=Op.STOP, + initcode_length=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: dict[Any, Account | None] = {} + if size <= fork.max_initcode_size(): + post[create_address] = Account(code=Op.STOP) + else: + tx.error = TransactionException.INITCODE_SIZE_EXCEEDED + post[create_address] = Account.NONEXISTENT + + state_test(pre=pre, tx=tx, post=post) + + +@pytest.mark.parametrize("initcode_size", INITCODE_SIZE_PARAMS) +@pytest.mark.with_all_create_opcodes() +def test_max_initcode_size_via_create( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + initcode_size: Callable[[Fork], int], + create_opcode: Op, +) -> None: + """Ensure the new max initcode size is enforced via create opcodes.""" + size = initcode_size(fork) + initcode = Initcode( + deploy_code=Op.STOP, + initcode_length=size, + ) + initcode_bytes = bytes(initcode) + + alice = pre.fund_eoa() + + create_call = ( + create_opcode( + value=0, offset=0, size=Op.CALLDATASIZE, salt=CREATE2_SALT + ) + if create_opcode == Op.CREATE2 + else create_opcode(value=0, offset=0, size=Op.CALLDATASIZE) + ) + + factory_code = ( + Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + + Op.SSTORE(0, create_call) + + Op.STOP + ) + + factory = pre.deploy_contract(factory_code) + + create_address = compute_create_address( + address=factory, + nonce=1, + salt=CREATE2_SALT, + initcode=initcode, + opcode=create_opcode, + ) + + tx = Transaction( + sender=alice, + to=factory, + data=initcode_bytes, + gas_limit=fork.transaction_gas_limit_cap(), + ) + + # Opcode-level: oversized initcode causes OutOfGasError + # (tx succeeds, CREATE returns 0) + created = size <= fork.max_initcode_size() + post: dict[Any, Account | None] = { + factory: Account(storage={0: create_address if created else 0}), + } + if created: + post[create_address] = Account(code=Op.STOP) + else: + post[create_address] = Account.NONEXISTENT + + state_test(pre=pre, tx=tx, post=post) + + +@pytest.mark.parametrize( + "gas_shortfall", + [ + pytest.param(0, id="exact_gas"), + pytest.param( + 1, + id="short_one_gas", + marks=pytest.mark.exception_test, + ), + ], +) +def test_max_initcode_size_gas_metering( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + gas_shortfall: int, +) -> None: + """Verify initcode gas metering at the new max initcode size.""" + initcode = Initcode( + deploy_code=Op.STOP, initcode_length=fork.max_initcode_size() + ) + alice = pre.fund_eoa() + + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + calldata=initcode, contract_creation=True + ) + + tx = Transaction( + sender=alice, + to=None, + data=initcode, + gas_limit=intrinsic_gas - gas_shortfall, + error=TransactionException.INTRINSIC_GAS_TOO_LOW + if gas_shortfall + else None, + ) + + post = { + compute_create_address(address=alice, nonce=0): Account.NONEXISTENT + if gas_shortfall + else Account(code=Op.STOP), + } + + state_test(pre=pre, tx=tx, post=post) + + +@pytest.mark.parametrize( + "gas_shortfall", + [ + pytest.param(0, id="exact_gas"), + pytest.param(1, id="short_one_gas"), + ], +) +@pytest.mark.with_all_create_opcodes() +def test_max_initcode_size_gas_metering_via_create( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + gas_shortfall: int, + create_opcode: Op, +) -> None: + """Verify gas metering via create opcodes for the new initcode size.""" + initcode = Initcode( + deploy_code=Op.STOP, initcode_length=fork.max_initcode_size() + ) + alice = pre.fund_eoa() + + initcode_len = len(initcode) + create_call = ( + create_opcode( + value=0, + offset=0, + size=Op.CALLDATASIZE, + salt=CREATE2_SALT, + init_code_size=initcode_len, + ) + if create_opcode == Op.CREATE2 + else create_opcode( + value=0, + offset=0, + size=Op.CALLDATASIZE, + init_code_size=initcode_len, + ) + ) + + # Factory stores CREATE return value in slot 0 + factory_code = ( + Op.CALLDATACOPY( + 0, + 0, + Op.CALLDATASIZE, + data_size=initcode_len, + new_memory_size=initcode_len, + ) + + Op.SSTORE(0, create_call) + + Op.STOP + ) + + factory = pre.deploy_contract(factory_code) + + create_address = compute_create_address( + address=factory, + nonce=1, + salt=CREATE2_SALT, + initcode=initcode, + opcode=create_opcode, + ) + + # Compute exact gas the factory needs + factory_gas = ( + factory_code.gas_cost(fork) + + initcode.execution_gas(fork) + + initcode.deployment_gas(fork) + ) + + # Caller CALLs factory with explicit gas to bypass EIP-7623 floor data + # cost and the 63/64 rule (EIP-150). + caller = pre.deploy_contract( + Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + + Op.CALL( + gas=factory_gas - gas_shortfall, + address=factory, + value=0, + args_offset=0, + args_size=Op.CALLDATASIZE, + ret_offset=0, + ret_size=0, + ) + + Op.STOP + ) + + tx = Transaction( + sender=alice, + to=caller, + data=bytes(initcode), + gas_limit=fork.transaction_gas_limit_cap(), + ) + + # With shortfall, factory OOGs and all state reverts + created = not gas_shortfall + post = { + create_address: Account(code=Op.STOP) + if created + else Account.NONEXISTENT, + factory: Account(storage={0: create_address if created else 0}), + } + + state_test(pre=pre, tx=tx, post=post) From cb030f470da7ab074bfbbc31b78c00a6034e4861 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Fri, 27 Feb 2026 18:32:24 +0100 Subject: [PATCH 2/5] fix(tests): Failing initcode test (#2355) --- tests/shanghai/eip3860_initcode/test_initcode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/shanghai/eip3860_initcode/test_initcode.py b/tests/shanghai/eip3860_initcode/test_initcode.py index 08e56b48c48..c3196ebb128 100644 --- a/tests/shanghai/eip3860_initcode/test_initcode.py +++ b/tests/shanghai/eip3860_initcode/test_initcode.py @@ -238,7 +238,7 @@ def tx_access_list(self) -> List[AccessList]: """ return [ AccessList(address=Address(i), storage_keys=[]) - for i in range(1, 478) + for i in range(1, 642) ] @pytest.fixture From f55fc5458372763cd7df19bfecfe0172062752e5 Mon Sep 17 00:00:00 2001 From: spencer-tb Date: Thu, 12 Mar 2026 16:26:57 +0000 Subject: [PATCH 3/5] refactor(tests): remove gas metering test moved to EIP-8037 The 2D gas metering test for max initcode size via CREATE is an EIP-8037 concern and now lives in the 8037 test suite. The 7954 max initcode enforcement is covered by test_max_initcode_size_via_create. --- .../test_max_initcode_size.py | 104 ------------------ 1 file changed, 104 deletions(-) diff --git a/tests/amsterdam/eip7954_increase_max_contract_size/test_max_initcode_size.py b/tests/amsterdam/eip7954_increase_max_contract_size/test_max_initcode_size.py index 937ed015352..5e14820e63e 100644 --- a/tests/amsterdam/eip7954_increase_max_contract_size/test_max_initcode_size.py +++ b/tests/amsterdam/eip7954_increase_max_contract_size/test_max_initcode_size.py @@ -185,107 +185,3 @@ def test_max_initcode_size_gas_metering( } state_test(pre=pre, tx=tx, post=post) - - -@pytest.mark.parametrize( - "gas_shortfall", - [ - pytest.param(0, id="exact_gas"), - pytest.param(1, id="short_one_gas"), - ], -) -@pytest.mark.with_all_create_opcodes() -def test_max_initcode_size_gas_metering_via_create( - state_test: StateTestFiller, - pre: Alloc, - fork: Fork, - gas_shortfall: int, - create_opcode: Op, -) -> None: - """Verify gas metering via create opcodes for the new initcode size.""" - initcode = Initcode( - deploy_code=Op.STOP, initcode_length=fork.max_initcode_size() - ) - alice = pre.fund_eoa() - - initcode_len = len(initcode) - create_call = ( - create_opcode( - value=0, - offset=0, - size=Op.CALLDATASIZE, - salt=CREATE2_SALT, - init_code_size=initcode_len, - ) - if create_opcode == Op.CREATE2 - else create_opcode( - value=0, - offset=0, - size=Op.CALLDATASIZE, - init_code_size=initcode_len, - ) - ) - - # Factory stores CREATE return value in slot 0 - factory_code = ( - Op.CALLDATACOPY( - 0, - 0, - Op.CALLDATASIZE, - data_size=initcode_len, - new_memory_size=initcode_len, - ) - + Op.SSTORE(0, create_call) - + Op.STOP - ) - - factory = pre.deploy_contract(factory_code) - - create_address = compute_create_address( - address=factory, - nonce=1, - salt=CREATE2_SALT, - initcode=initcode, - opcode=create_opcode, - ) - - # Compute exact gas the factory needs - factory_gas = ( - factory_code.gas_cost(fork) - + initcode.execution_gas(fork) - + initcode.deployment_gas(fork) - ) - - # Caller CALLs factory with explicit gas to bypass EIP-7623 floor data - # cost and the 63/64 rule (EIP-150). - caller = pre.deploy_contract( - Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) - + Op.CALL( - gas=factory_gas - gas_shortfall, - address=factory, - value=0, - args_offset=0, - args_size=Op.CALLDATASIZE, - ret_offset=0, - ret_size=0, - ) - + Op.STOP - ) - - tx = Transaction( - sender=alice, - to=caller, - data=bytes(initcode), - gas_limit=fork.transaction_gas_limit_cap(), - ) - - # With shortfall, factory OOGs and all state reverts - created = not gas_shortfall - post = { - create_address: Account(code=Op.STOP) - if created - else Account.NONEXISTENT, - factory: Account(storage={0: create_address if created else 0}), - } - - state_test(pre=pre, tx=tx, post=post) From bbc0ad782e51a703ad244b29ba41120e23bdf62f Mon Sep 17 00:00:00 2001 From: marioevz Date: Tue, 24 Mar 2026 17:34:51 -0600 Subject: [PATCH 4/5] fix(tests): Merge issues --- .../test_fork_transition.py | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/tests/amsterdam/eip7954_increase_max_contract_size/test_fork_transition.py b/tests/amsterdam/eip7954_increase_max_contract_size/test_fork_transition.py index 29042509f8b..0ea5fb5a7e0 100644 --- a/tests/amsterdam/eip7954_increase_max_contract_size/test_fork_transition.py +++ b/tests/amsterdam/eip7954_increase_max_contract_size/test_fork_transition.py @@ -14,11 +14,11 @@ Alloc, Block, BlockchainTestFiller, - Fork, Initcode, Op, Transaction, TransactionException, + TransitionFork, compute_create_address, ) @@ -35,10 +35,10 @@ def test_max_code_size_fork_transition( blockchain_test: BlockchainTestFiller, pre: Alloc, - fork: Fork, + fork: TransitionFork, ) -> None: """Ensure the new max code size limit activates at the fork boundary.""" - code_size = fork.max_code_size() + code_size = fork.transitions_to().max_code_size() deploy_code = Op.JUMPDEST * code_size initcode = Initcode(deploy_code=deploy_code) @@ -56,7 +56,7 @@ def test_max_code_size_fork_transition( sender=alice, to=None, data=initcode, - gas_limit=fork.transaction_gas_limit_cap(), + gas_limit=fork.transitions_from().transaction_gas_limit_cap(), ) ], ), @@ -67,7 +67,7 @@ def test_max_code_size_fork_transition( sender=bob, to=None, data=initcode, - gas_limit=fork.transaction_gas_limit_cap(), + gas_limit=fork.transitions_to().transaction_gas_limit_cap(), ) ], ), @@ -81,15 +81,15 @@ def test_max_code_size_fork_transition( blockchain_test(pre=pre, blocks=blocks, post=post) -@pytest.mark.with_all_create_opcodes() +@pytest.mark.parametrize("create_opcode", [Op.CREATE, Op.CREATE2]) def test_max_code_size_via_create_fork_transition( blockchain_test: BlockchainTestFiller, pre: Alloc, - fork: Fork, + fork: TransitionFork, create_opcode: Op, ) -> None: """Ensure the new max code size limit activates at the fork via opcodes.""" - code_size = fork.max_code_size() + code_size = fork.transitions_to().max_code_size() deploy_code = Op.JUMPDEST * code_size initcode = Initcode(deploy_code=deploy_code) initcode_bytes = bytes(initcode) @@ -137,7 +137,7 @@ def test_max_code_size_via_create_fork_transition( sender=alice, to=factory_pre, data=initcode_bytes, - gas_limit=fork.transaction_gas_limit_cap(), + gas_limit=fork.transitions_from().transaction_gas_limit_cap(), ) ], ), @@ -148,7 +148,7 @@ def test_max_code_size_via_create_fork_transition( sender=bob, to=factory_post, data=initcode_bytes, - gas_limit=fork.transaction_gas_limit_cap(), + gas_limit=fork.transitions_to().transaction_gas_limit_cap(), ) ], ), @@ -166,12 +166,12 @@ def test_max_code_size_via_create_fork_transition( def test_max_initcode_size_fork_transition( blockchain_test: BlockchainTestFiller, pre: Alloc, - fork: Fork, + fork: TransitionFork, ) -> None: """Ensure the new max initcode size limit activates exactly at the fork.""" initcode = Initcode( deploy_code=Op.STOP, - initcode_length=fork.max_initcode_size(), + initcode_length=fork.transitions_to().max_initcode_size(), ) alice = pre.fund_eoa() @@ -191,7 +191,7 @@ def test_max_initcode_size_fork_transition( sender=alice, to=None, data=initcode, - gas_limit=fork.transaction_gas_limit_cap(), + gas_limit=fork.transitions_from().transaction_gas_limit_cap(), error=initcode_too_large, ) ], @@ -205,7 +205,7 @@ def test_max_initcode_size_fork_transition( sender=bob, to=None, data=initcode, - gas_limit=fork.transaction_gas_limit_cap(), + gas_limit=fork.transitions_to().transaction_gas_limit_cap(), ) ], ), @@ -218,17 +218,17 @@ def test_max_initcode_size_fork_transition( blockchain_test(pre=pre, blocks=blocks, post=post) -@pytest.mark.with_all_create_opcodes() +@pytest.mark.parametrize("create_opcode", [Op.CREATE, Op.CREATE2]) def test_max_initcode_size_via_create_fork_transition( blockchain_test: BlockchainTestFiller, pre: Alloc, - fork: Fork, + fork: TransitionFork, create_opcode: Op, ) -> None: """Ensure the new max initcode size limit activates at fork via opcodes.""" initcode = Initcode( deploy_code=Op.STOP, - initcode_length=fork.max_initcode_size(), + initcode_length=fork.transitions_to().max_initcode_size(), ) initcode_bytes = bytes(initcode) @@ -275,7 +275,7 @@ def test_max_initcode_size_via_create_fork_transition( sender=alice, to=factory_pre, data=initcode_bytes, - gas_limit=fork.transaction_gas_limit_cap(), + gas_limit=fork.transitions_from().transaction_gas_limit_cap(), ) ], ), @@ -286,7 +286,7 @@ def test_max_initcode_size_via_create_fork_transition( sender=bob, to=factory_post, data=initcode_bytes, - gas_limit=fork.transaction_gas_limit_cap(), + gas_limit=fork.transitions_to().transaction_gas_limit_cap(), ) ], ), @@ -308,13 +308,13 @@ def test_max_initcode_size_via_create_fork_transition( def test_max_code_size_with_max_initcode_fork_transition( blockchain_test: BlockchainTestFiller, pre: Alloc, - fork: Fork, + fork: TransitionFork, ) -> None: """Ensure max code + max initcode activates at the fork boundary.""" - deploy_code = Op.JUMPDEST * fork.max_code_size() + deploy_code = Op.JUMPDEST * fork.transitions_to().max_code_size() initcode = Initcode( deploy_code=deploy_code, - initcode_length=fork.max_initcode_size(), + initcode_length=fork.transitions_to().max_initcode_size(), ) alice = pre.fund_eoa() @@ -332,7 +332,7 @@ def test_max_code_size_with_max_initcode_fork_transition( sender=alice, to=None, data=initcode, - gas_limit=fork.transaction_gas_limit_cap(), + gas_limit=fork.transitions_from().transaction_gas_limit_cap(), error=initcode_too_large, ) ], @@ -345,7 +345,7 @@ def test_max_code_size_with_max_initcode_fork_transition( sender=bob, to=None, data=initcode, - gas_limit=fork.transaction_gas_limit_cap(), + gas_limit=fork.transitions_to().transaction_gas_limit_cap(), ) ], ), @@ -361,10 +361,10 @@ def test_max_code_size_with_max_initcode_fork_transition( def test_parent_max_code_size_across_fork( blockchain_test: BlockchainTestFiller, pre: Alloc, - fork: Fork, + fork: TransitionFork, ) -> None: """Ensure previous max code size works after transition.""" - parent = fork.parent() + parent = fork.transitions_from() assert parent is not None, "Parent fork must be defined for this test" code_size = parent.max_code_size() @@ -385,7 +385,7 @@ def test_parent_max_code_size_across_fork( sender=alice, to=None, data=initcode, - gas_limit=fork.transaction_gas_limit_cap(), + gas_limit=fork.transitions_from().transaction_gas_limit_cap(), ) ], ), @@ -396,7 +396,7 @@ def test_parent_max_code_size_across_fork( sender=bob, to=None, data=initcode, - gas_limit=fork.transaction_gas_limit_cap(), + gas_limit=fork.transitions_to().transaction_gas_limit_cap(), ) ], ), From 9d5f65adabc8a993a3312849110915136a1d529d Mon Sep 17 00:00:00 2001 From: marioevz Date: Wed, 8 Apr 2026 17:09:02 -0600 Subject: [PATCH 5/5] refactor(tests): Condition EIP-7954 to EIP Inclusion --- .../eip7954_increase_max_contract_size/test_eip_mainnet.py | 2 +- .../test_fork_transition.py | 4 ++-- .../eip7954_increase_max_contract_size/test_max_code_size.py | 2 +- .../test_max_initcode_size.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/amsterdam/eip7954_increase_max_contract_size/test_eip_mainnet.py b/tests/amsterdam/eip7954_increase_max_contract_size/test_eip_mainnet.py index c37a919bb18..7d0ff0e0a36 100644 --- a/tests/amsterdam/eip7954_increase_max_contract_size/test_eip_mainnet.py +++ b/tests/amsterdam/eip7954_increase_max_contract_size/test_eip_mainnet.py @@ -24,7 +24,7 @@ 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] +pytestmark = [pytest.mark.valid_at("EIP7954"), pytest.mark.mainnet] def test_over_max_code_size_mainnet( diff --git a/tests/amsterdam/eip7954_increase_max_contract_size/test_fork_transition.py b/tests/amsterdam/eip7954_increase_max_contract_size/test_fork_transition.py index 0ea5fb5a7e0..4000ed7df4a 100644 --- a/tests/amsterdam/eip7954_increase_max_contract_size/test_fork_transition.py +++ b/tests/amsterdam/eip7954_increase_max_contract_size/test_fork_transition.py @@ -3,7 +3,7 @@ [EIP-7954: Increase Maximum Contract Size](https://eips.ethereum.org/EIPS/eip-7954). Tests that the new max code size and initcode size limits activate -exactly at the Amsterdam fork boundary (timestamp 15,000). +exactly at the EIP7954 fork boundary (timestamp 15,000). """ from typing import Any @@ -27,7 +27,7 @@ REFERENCE_SPEC_GIT_PATH = ref_spec_7954.git_path REFERENCE_SPEC_VERSION = ref_spec_7954.version -pytestmark = pytest.mark.valid_at_transition_to("Amsterdam") +pytestmark = pytest.mark.valid_at_transition_to("EIP7954") CREATE2_SALT = 0xC0FFEE diff --git a/tests/amsterdam/eip7954_increase_max_contract_size/test_max_code_size.py b/tests/amsterdam/eip7954_increase_max_contract_size/test_max_code_size.py index 737831cc0fb..0eb46b61ae4 100644 --- a/tests/amsterdam/eip7954_increase_max_contract_size/test_max_code_size.py +++ b/tests/amsterdam/eip7954_increase_max_contract_size/test_max_code_size.py @@ -22,7 +22,7 @@ REFERENCE_SPEC_GIT_PATH = ref_spec_7954.git_path REFERENCE_SPEC_VERSION = ref_spec_7954.version -pytestmark = pytest.mark.valid_from("Amsterdam") +pytestmark = pytest.mark.valid_from("EIP7954") CREATE2_SALT = 0xC0FFEE diff --git a/tests/amsterdam/eip7954_increase_max_contract_size/test_max_initcode_size.py b/tests/amsterdam/eip7954_increase_max_contract_size/test_max_initcode_size.py index 5e14820e63e..f3a469c43a5 100644 --- a/tests/amsterdam/eip7954_increase_max_contract_size/test_max_initcode_size.py +++ b/tests/amsterdam/eip7954_increase_max_contract_size/test_max_initcode_size.py @@ -24,7 +24,7 @@ REFERENCE_SPEC_GIT_PATH = ref_spec_7954.git_path REFERENCE_SPEC_VERSION = ref_spec_7954.version -pytestmark = pytest.mark.valid_from("Amsterdam") +pytestmark = pytest.mark.valid_from("EIP7954") CREATE2_SALT = 0xC0FFEE