From 6ed28f0eb0d541b4ce469be1f94f21d72d9c3a7a Mon Sep 17 00:00:00 2001 From: PC Date: Tue, 8 Sep 2026 11:50:04 +0200 Subject: [PATCH] feat(tests): EIP-7954 checklist triage and parent initcode-limit transition test Bring the EIP-7954 checklist from 0% to 100%: tag the existing size-limit tests with the checklist items they pin (initcode behaviour by opcode and by creation transaction, opcode out-of-bounds at max and max+1, created address, activation at the fork, and the modified initcode validity constraint before/after the fork), and record the rest as not applicable or as covered elsewhere. External coverage points at the EIP-8037 and EIP-3860 suites for the opcode-level gas boundaries and exceptional abort at the new sizes, so those are not duplicated here. Add the one missing arm: a create transaction whose initcode is exactly the parent fork's maximum is accepted both before and after the transition, mirroring the existing parent max-code-size test. Co-Authored-By: Claude Fable 5.1 --- .../eip_checklist_external_coverage.txt | 10 +++ .../eip_checklist_not_applicable.txt | 33 ++++++++++ .../test_eip_mainnet.py | 2 + .../test_fork_transition.py | 61 +++++++++++++++++++ .../test_max_code_size.py | 9 +++ .../test_max_initcode_size.py | 17 +++++- 6 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 tests/amsterdam/eip7954_increase_max_contract_size/eip_checklist_external_coverage.txt create mode 100644 tests/amsterdam/eip7954_increase_max_contract_size/eip_checklist_not_applicable.txt diff --git a/tests/amsterdam/eip7954_increase_max_contract_size/eip_checklist_external_coverage.txt b/tests/amsterdam/eip7954_increase_max_contract_size/eip_checklist_external_coverage.txt new file mode 100644 index 00000000000..a682e9df6a8 --- /dev/null +++ b/tests/amsterdam/eip7954_increase_max_contract_size/eip_checklist_external_coverage.txt @@ -0,0 +1,10 @@ +general/code_coverage/eels = EIP-7954 changes only MAX_CODE_SIZE and MAX_INIT_CODE_SIZE in src/ethereum/forks/amsterdam/vm/interpreter.py; the three lines that consume them (the code-deposit size check in process_create_message, the creation-transaction initcode check in transactions.validate_transaction, and the CREATE/CREATE2 initcode checks in vm/instructions/system.py) are each executed on both sides of the limit by the at_max/over_max cases +general/code_coverage/test_coverage = Run fill tests/amsterdam/eip7954_increase_max_contract_size --cov=tests/amsterdam/eip7954_increase_max_contract_size; the at_max/over_max, CREATE/CREATE2 and exact_gas/short_one_gas parametrizations take both arms of every post-state conditional and the suite has no unreachable guard lines +general/code_coverage/missed_lines = No EIP-7954 line is missed; the neighbouring lines in the same functions that a coverage report may show are other EIPs' scope (EIP-3541 0xEF prefix check, EIP-8037/EIP-2780 code-deposit keccak and state-gas charges in process_create_message) +opcode/test/gas_usage/normal = CREATE/CREATE2 gas is unchanged in formula but is now charged over a 128 KiB initcode / 64 KiB deposit. Opcode-level exact gas at the new bound on Amsterdam: tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py::test_max_initcode_size_gas_metering_via_create[exact_gas] (CREATE and CREATE2, initcode_length=fork.max_initcode_size(), caller forwards exactly factory_code.gas_cost + initcode.e +opcode/test/gas_usage/memory_expansion = The expansion formula is untouched; what changed is that CREATE/CREATE2 may now legitimately expand memory by 128 KiB for initcode. Pinned at exactly that size on Amsterdam by tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py::test_oversized_initcode_opcode_no_state_gas[at_max] (CREATE/CREATE2 itself expands memory by fork.max_initcode_size() with new_memory_size metadata; the caller f +opcode/test/gas_usage/out_of_gas_execution = Opcode-level one-gas-short and over-limit-input OOG at the new bounds on Amsterdam: tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py::test_max_initcode_size_gas_metering_via_create[short_one_gas] (CREATE/CREATE2 with a 128 KiB initcode, factory one execution gas short of the exact sequence: no contract, factory slot stays 0) and ::test_oversized_initcode_opcode_no_state_gas[over_max] +opcode/test/gas_usage/order_of_operations/exact = CREATE/CREATE2 keep the same charge order (size check, then base + initcode word cost + memory expansion in the caller, 63/64 forward, initcode execution, code deposit in the child); only the constants moved. Exact-fit of the caller-side stipend at 128 KiB: tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py::test_oversized_initcode_opcode_no_state_gas[at_max] (forwards exactly CREATE ba +opcode/test/gas_usage/order_of_operations/oog = One gas short of the whole CREATE/CREATE2 sequence at 128 KiB initcode on Amsterdam: tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py::test_max_initcode_size_gas_metering_via_create[short_one_gas] (creation fails, no contract, factory slot 0). This is an aggregate one-short; the per-charge isolation at the new maxima (caller stipend -1 and 64 KiB deposit -1) does not exist anywhere an +opcode/test/exceptional_abort = An initcode longer than MAX_INITCODE_SIZE passed to CREATE/CREATE2 is an exceptional abort of the calling frame (all gas consumed), and the threshold is what EIP-7954 moves. Pinned at fork.max_initcode_size()+1 on Amsterdam by tests/shanghai/eip3860_initcode/test_initcode.py::TestCreateInitcode::test_create_opcode_initcode[over_limit_zeros|over_limit_ones x CREATE|CREATE2] (the wrapping CALL returns 0 and the creator +opcode/test/contract_creation/failure/oog = Creation-by-opcode out of gas at the new maxima on Amsterdam is pinned by tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py::test_max_initcode_size_gas_metering_via_create[short_one_gas] (CREATE and CREATE2 over a max-size initcode, one gas short: no contract, factory slot stays 0) diff --git a/tests/amsterdam/eip7954_increase_max_contract_size/eip_checklist_not_applicable.txt b/tests/amsterdam/eip7954_increase_max_contract_size/eip_checklist_not_applicable.txt new file mode 100644 index 00000000000..6a87ffe0379 --- /dev/null +++ b/tests/amsterdam/eip7954_increase_max_contract_size/eip_checklist_not_applicable.txt @@ -0,0 +1,33 @@ +general/code_coverage/second_client = Optional +opcode/test/mem_exp = EIP does not add an opcode; CREATE/CREATE2 read initcode from memory exactly as before, only the accepted initcode length grows +opcode/test/stack_overflow = CREATE/CREATE2 stack semantics are unchanged, only the size constants move +opcode/test/stack_underflow = CREATE/CREATE2 stack semantics are unchanged, only the size constants move +opcode/test/stack_complex_operations = CREATE/CREATE2 have no data portion and perform only simple pop/push +opcode/test/execution_context/call = The initcode-size and code-deposit checks do not depend on the frame kind that runs CREATE/CREATE2 +opcode/test/execution_context/staticcall = CREATE/CREATE2 have been banned in static context since EIP-214, untouched here +opcode/test/execution_context/delegatecall = CREATE/CREATE2 behaviour under DELEGATECALL is unchanged +opcode/test/execution_context/callcode = CREATE/CREATE2 behaviour under CALLCODE is unchanged +opcode/test/execution_context/initcode/reentry = No stateful behaviour is added; re-creation over the same initcode and address is unchanged +opcode/test/execution_context/set_code = EIP-7702 delegation designators are 23 bytes; delegated execution is untouched by the raised limits +opcode/test/execution_context/tx_context = The size limits do not depend on any transaction property +opcode/test/execution_context/block_context = The size limits do not depend on any block property; activation is tracked under opcode/test/fork_transition/at +opcode/test/return_data = Return-buffer behaviour of CREATE/CREATE2 is unchanged, an over-limit RETURN was already an exceptional halt +opcode/test/gas_usage/out_of_gas_memory = The memory-expansion out-of-gas rule is untouched; the moved size checks are tracked under out_of_bounds +opcode/test/terminating = CREATE/CREATE2 are not terminating opcodes and initcode RETURN/REVERT/halt semantics are unchanged +opcode/test/data_portion = CREATE/CREATE2 have no data portion +opcode/test/contract_creation/failure/insufficient_value = The value/balance check of CREATE/CREATE2 is untouched +opcode/test/contract_creation/failure/collision = The address-collision rule of CREATE/CREATE2 is untouched +opcode/test/contract_creation/recursive = Recursive creation behaviour is untouched +opcode/test/fork_transition/invalid = CREATE/CREATE2 are valid before and after the fork; only the limits change, tracked under fork_transition/at +precompile = EIP does not introduce a new precompile +removed_precompile = EIP does not remove a precompile +system_contract = EIP does not introduce a new system contract +transaction_type = EIP does not introduce a new transaction type; the raised initcode bound on create transactions is tracked under modified_transaction_validity_constraint +block_header_field = EIP does not add any new block header fields +block_body_field = EIP does not add any new block body fields +gas_cost_changes = EIP changes no gas schedule value, only MAX_CODE_SIZE and MAX_INITCODE_SIZE +gas_refunds_changes = EIP does not change any gas refund rule +blob_count_changes = EIP does not introduce any blob count changes +execution_layer_request = EIP does not introduce an execution layer request +new_transaction_validity_constraint = EIP introduces no new validity constraint, it raises the existing EIP-3860 initcode threshold (see modified_transaction_validity_constraint) +block_level_constraint = EIP introduces no block-level validation rule; both limits are checked per contract creation 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 adcbdedadcc..1e12141f24f 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 @@ -9,6 +9,7 @@ from execution_testing import ( Account, Alloc, + EIPChecklist, Fork, Initcode, Op, @@ -55,6 +56,7 @@ def test_over_max_code_size_mainnet( @pytest.mark.inclusion_test @pytest.mark.exception_test +@EIPChecklist.ModifiedTransactionValidityConstraint.Test.ForkTransition.RejectedAfterFork() def test_over_max_initcode_size_mainnet( state_test: StateTestFiller, pre: Alloc, 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 90aabfa5560..02e2cd55399 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,6 +14,7 @@ Alloc, Block, BlockchainTestFiller, + EIPChecklist, Initcode, Op, Transaction, @@ -82,6 +83,8 @@ def test_max_code_size_fork_transition( @pytest.mark.parametrize("create_opcode", [Op.CREATE, Op.CREATE2]) +@EIPChecklist.Opcode.Test.ForkTransition.At() +@EIPChecklist.Opcode.Test.ContractCreation.Address() def test_max_code_size_via_create_fork_transition( blockchain_test: BlockchainTestFiller, pre: Alloc, @@ -163,6 +166,8 @@ def test_max_code_size_via_create_fork_transition( @pytest.mark.exception_test +@EIPChecklist.ModifiedTransactionValidityConstraint.Test.ForkTransition.RejectedBeforeFork() +@EIPChecklist.ModifiedTransactionValidityConstraint.Test.ForkTransition.AcceptedAfterFork() def test_max_initcode_size_fork_transition( blockchain_test: BlockchainTestFiller, pre: Alloc, @@ -217,6 +222,8 @@ def test_max_initcode_size_fork_transition( @pytest.mark.parametrize("create_opcode", [Op.CREATE, Op.CREATE2]) +@EIPChecklist.Opcode.Test.ForkTransition.At() +@EIPChecklist.Opcode.Test.ContractCreation.Address() def test_max_initcode_size_via_create_fork_transition( blockchain_test: BlockchainTestFiller, pre: Alloc, @@ -301,6 +308,8 @@ def test_max_initcode_size_via_create_fork_transition( @pytest.mark.exception_test +@EIPChecklist.ModifiedTransactionValidityConstraint.Test.ForkTransition.RejectedBeforeFork() +@EIPChecklist.ModifiedTransactionValidityConstraint.Test.ForkTransition.AcceptedAfterFork() def test_max_code_size_with_max_initcode_fork_transition( blockchain_test: BlockchainTestFiller, pre: Alloc, @@ -400,3 +409,55 @@ def test_parent_max_code_size_across_fork( } blockchain_test(pre=pre, blocks=blocks, post=post) + + +@EIPChecklist.ModifiedTransactionValidityConstraint.Test.ForkTransition.AcceptedBeforeFork() # noqa: E501 +def test_parent_max_initcode_size_across_fork( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: TransitionFork, +) -> None: + """Ensure initcode at the previous max size is valid on both sides.""" + parent = fork.transitions_from() + assert parent is not None, "Parent fork must be defined for this test" + + initcode = Initcode( + deploy_code=Op.STOP, + initcode_length=parent.max_initcode_size(), + ) + + 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, + ) + ], + ), + Block( + timestamp=15_000, + txs=[ + Transaction( + sender=bob, + to=None, + data=initcode, + ) + ], + ), + ] + + post: dict[Any, Account | None] = { + create_address_pre: Account(code=Op.STOP), + create_address_post: Account(code=Op.STOP), + } + + 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 index d6b52684e67..d801945346b 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 @@ -10,6 +10,7 @@ Alloc, Bytecode, CodeGasMeasure, + EIPChecklist, Fork, Initcode, Op, @@ -66,6 +67,11 @@ def test_max_code_size( @pytest.mark.parametrize("deploy_code_size", DEPLOY_CODE_SIZE_PARAMS) @pytest.mark.with_all_create_opcodes() +@EIPChecklist.Opcode.Test.ExecutionContext.Initcode.Behavior.Opcode() +@EIPChecklist.Opcode.Test.GasUsage.ExtraGas() +@EIPChecklist.Opcode.Test.OutOfBounds.Verify.Max() +@EIPChecklist.Opcode.Test.OutOfBounds.Verify.MaxPlusOne() +@EIPChecklist.Opcode.Test.ContractCreation.Address() def test_max_code_size_via_create( state_test: StateTestFiller, pre: Alloc, @@ -178,6 +184,8 @@ def test_max_code_size_deposit_gas( state_test(pre=pre, tx=tx, post=post) +@EIPChecklist.Opcode.Test.ExecutionContext.Initcode.Behavior() +@EIPChecklist.Opcode.Test.ExecutionContext.Initcode.Behavior.Tx() def test_max_code_size_with_max_initcode( state_test: StateTestFiller, pre: Alloc, @@ -285,6 +293,7 @@ def test_max_code_size_self_opcodes( pytest.param(Op.CREATE2, id="CREATE2"), ], ) +@EIPChecklist.Opcode.Test.OutOfBounds.Verify.MaxPlusOne() def test_warm_after_failed_create_over_max_code_size( state_test: StateTestFiller, pre: Alloc, 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 bd83b30c467..988ddf1c94e 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 @@ -10,6 +10,7 @@ from execution_testing import ( Account, Alloc, + EIPChecklist, Fork, Initcode, Op, @@ -34,11 +35,18 @@ ] TX_INITCODE_SIZE_PARAMS = [ - pytest.param(lambda f: f.max_initcode_size(), id="at_max"), + pytest.param( + lambda f: f.max_initcode_size(), + id="at_max", + marks=EIPChecklist.Opcode.Test.ExecutionContext.Initcode.Behavior.Tx(), + ), pytest.param( lambda f: f.max_initcode_size() + 1, id="over_max", - marks=pytest.mark.exception_test, + marks=[ + pytest.mark.exception_test, + EIPChecklist.ModifiedTransactionValidityConstraint.Test.ForkTransition.RejectedAfterFork(), + ], ), ] @@ -80,6 +88,11 @@ def test_max_initcode_size( @pytest.mark.parametrize("initcode_size", INITCODE_SIZE_PARAMS) @pytest.mark.with_all_create_opcodes() +@EIPChecklist.Opcode.Test.ExecutionContext.Initcode.Behavior.Opcode() +@EIPChecklist.Opcode.Test.GasUsage.ExtraGas() +@EIPChecklist.Opcode.Test.OutOfBounds.Verify.Max() +@EIPChecklist.Opcode.Test.OutOfBounds.Verify.MaxPlusOne() +@EIPChecklist.Opcode.Test.ContractCreation.Address() def test_max_initcode_size_via_create( state_test: StateTestFiller, pre: Alloc,